From 5458b842bf014a681aab3a5ae97488f89a18b7a3 Mon Sep 17 00:00:00 2001 From: Falko Schindler Date: Fri, 24 Nov 2023 08:33:07 +0100 Subject: [PATCH] rewrite label documentation --- main.py | 21 +----- website/documentation/content/overview.py | 18 ++--- website/documentation/model.py | 72 +++++++++++++++++-- .../documentation/more/label_documentation.py | 39 +++++----- website/documentation/rendering.py | 8 ++- .../documentation/sections/text_elements.py | 8 +++ website/documentation_pages.py | 23 +++++- 7 files changed, 136 insertions(+), 53 deletions(-) diff --git a/main.py b/main.py index 76fc51717..1c9527a70 100755 --- a/main.py +++ b/main.py @@ -30,24 +30,9 @@ async def post_dark_mode(request: Request) -> None: app.storage.browser['dark_mode'] = (await request.json()).get('value') -@ui.page('/') -def main_page_() -> None: - main_page.create() - - -@ui.page('/documentation') -def documentation_page() -> None: - documentation_pages.create_overview() - - -@ui.page('/documentation/section_{name}') -def documentation_section(name: str) -> None: - documentation_pages.create_section(name) - - -@ui.page('/documentation/{name}') -async def documentation_page_more(name: str) -> None: - await documentation_pages.create_more(name) +ui.page('/')(main_page.create) +ui.page('/documentation')(documentation_pages.create_overview) +ui.page('/documentation/{name}')(documentation_pages.create_page) @app.get('/status') diff --git a/website/documentation/content/overview.py b/website/documentation/content/overview.py index 48338baa0..bd0c2622e 100644 --- a/website/documentation/content/overview.py +++ b/website/documentation/content/overview.py @@ -11,15 +11,15 @@ section.name: section for section in [ text_elements, - controls, - audiovisual_elements, - data_elements, - binding_properties, - page_layout, - styling_appearance, - action_events, - pages_routing, - configuration_deployment, + # controls, + # audiovisual_elements, + # data_elements, + # binding_properties, + # page_layout, + # styling_appearance, + # action_events, + # pages_routing, + # configuration_deployment, ] } diff --git a/website/documentation/model.py b/website/documentation/model.py index d7f2d5445..4e29536e6 100644 --- a/website/documentation/model.py +++ b/website/documentation/model.py @@ -1,20 +1,30 @@ +from __future__ import annotations + import abc +import re from dataclasses import dataclass from typing import Callable, Iterator, List, Optional +import docutils.core + from nicegui.dataclasses import KWONLY_SLOTS +from nicegui.elements.markdown import apply_tailwind, remove_indentation @dataclass(**KWONLY_SLOTS) class DocumentationPart: title: Optional[str] = None description: Optional[str] = None + link: Optional[str] = None function: Optional[Callable] = None class Documentation(abc.ABC): + TITLE: Optional[str] = None - def __init__(self) -> None: + def __init__(self, route: str, back_link: Optional[str] = None) -> None: + self.route = route + self.back_link = back_link self._content: List[DocumentationPart] = [] self.content() @@ -25,14 +35,31 @@ def add_markdown(self, title: str, description: str) -> None: """Add a markdown section to the documentation.""" self._content.append(DocumentationPart(title=title, description=description)) - def add_element_demo(self, element: type) -> Callable[[Callable], Callable]: - """Add a demo section for an element to the documentation.""" + def add_markdown_demo(self, title: str, description: str) -> Callable[[Callable], Callable]: + """Add a markdown section to the documentation.""" def decorator(function: Callable) -> Callable: - part = DocumentationPart(title=element.__name__, description=element.__doc__ or '', function=function) - self._content.append(part) + self._content.append(DocumentationPart(title=title, description=description, function=function)) return function return decorator + def add_element_intro(self, documentation: ElementDocumentation) -> None: + """Add an element intro section to the documentation.""" + self.add_main_element_demo(documentation, intro_only=True) + + def add_main_element_demo(self, documentation: ElementDocumentation, *, intro_only: bool = False) -> None: + """Add a demo section for an element to the documentation.""" + title, doc = documentation.element.__init__.__doc__.split('\n', 1) # type: ignore + doc = remove_indentation(doc).replace('param ', '') + html = apply_tailwind(docutils.core.publish_parts(doc, writer_name='html5_polyglot')['html_body']) + if intro_only: + html = re.sub(r'
.*?
', '', html, flags=re.DOTALL) + self._content.append(DocumentationPart( + title=title, + description=html, + link=documentation.route if intro_only else None, + function=documentation.main_demo, + )) + def add_raw_nicegui(self, function: Callable) -> Callable: """Add a raw NiceGUI section to the documentation.""" self._content.append(DocumentationPart(function=function)) @@ -41,3 +68,38 @@ def add_raw_nicegui(self, function: Callable) -> Callable: @abc.abstractmethod def content(self) -> None: """Add documentation content here.""" + + +class SectionDocumentation(Documentation): + element_documentations: List[ElementDocumentation] + + def __init_subclass__(cls, title: str) -> None: + cls.TITLE = title + cls.element_documentations = [] + return super().__init_subclass__() + + def add_element_intro(self, documentation: ElementDocumentation) -> None: + self.element_documentations.append(documentation) + super().add_element_intro(documentation) + + +class ElementDocumentation(Documentation): + element: type + + def __init_subclass__(cls, element: type) -> None: + cls.element = element + return super().__init_subclass__() + + def __init__(self) -> None: + super().__init__(self.element.__name__.lower()) + + @abc.abstractmethod + def main_demo(self) -> None: + """Add a demo for the element here.""" + + def more_demos(self) -> None: + """Add more demos for the element here.""" + + def content(self) -> None: + self.add_main_element_demo(self) + self.more_demos() diff --git a/website/documentation/more/label_documentation.py b/website/documentation/more/label_documentation.py index df6244a17..d0613fe28 100644 --- a/website/documentation/more/label_documentation.py +++ b/website/documentation/more/label_documentation.py @@ -1,26 +1,27 @@ from nicegui import ui -from ..tools import text_demo +from ..model import ElementDocumentation -def main_demo() -> None: - ui.label('some label') +class LabelDocumentation(ElementDocumentation, element=ui.label): + def main_demo(self) -> None: + ui.label('some label') -def more() -> None: - @text_demo('Change Appearance Depending on the Content', ''' - You can overwrite the `_handle_text_change` method to update other attributes of a label depending on its content. - This technique also works for bindings as shown in the example below. - ''') - def status(): - class status_label(ui.label): - def _handle_text_change(self, text: str) -> None: - super()._handle_text_change(text) - if text == 'ok': - self.classes(replace='text-positive') - else: - self.classes(replace='text-negative') + def more_demos(self) -> None: + @self.add_markdown_demo('Change Appearance Depending on the Content', ''' + You can overwrite the `_handle_text_change` method to update other attributes of a label depending on its content. + This technique also works for bindings as shown in the example below. + ''') + def status(): + class status_label(ui.label): + def _handle_text_change(self, text: str) -> None: + super()._handle_text_change(text) + if text == 'ok': + self.classes(replace='text-positive') + else: + self.classes(replace='text-negative') - model = {'status': 'error'} - status_label().bind_text_from(model, 'status') - ui.switch(on_change=lambda e: model.update(status='ok' if e.value else 'error')) + model = {'status': 'error'} + status_label().bind_text_from(model, 'status') + ui.switch(on_change=lambda e: model.update(status='ok' if e.value else 'error')) diff --git a/website/documentation/rendering.py b/website/documentation/rendering.py index cb263da4a..bf9caf3af 100644 --- a/website/documentation/rendering.py +++ b/website/documentation/rendering.py @@ -10,9 +10,15 @@ def render(documentation: Documentation) -> None: add_header() ui.add_head_html('') with ui.column().classes('w-full p-8 lg:p-16 max-w-[1250px] mx-auto'): + if documentation.TITLE: + ui.markdown(f'# {documentation.TITLE}') for part in documentation: if part.title: - ui.markdown(f'### {part.title}') + if part.link: + with ui.link(target=part.link): + ui.markdown(f'### {part.title}') + else: + ui.markdown(f'### {part.title}') if part.description: ui.markdown(part.description) if part.function: diff --git a/website/documentation/sections/text_elements.py b/website/documentation/sections/text_elements.py index f2d9e0123..8c87a6a8b 100644 --- a/website/documentation/sections/text_elements.py +++ b/website/documentation/sections/text_elements.py @@ -1,5 +1,7 @@ from nicegui import ui +from ..model import SectionDocumentation +from ..more.label_documentation import LabelDocumentation from ..tools import load_demo name = 'text_elements' @@ -17,3 +19,9 @@ def content() -> None: load_demo(ui.markdown) load_demo(ui.mermaid) load_demo(ui.html) + + +class TextElementsDocumentation(SectionDocumentation, title='Text Elements'): + + def content(self) -> None: + self.add_element_intro(LabelDocumentation()) diff --git a/website/documentation_pages.py b/website/documentation_pages.py index 8a589be4e..196d0edd0 100644 --- a/website/documentation_pages.py +++ b/website/documentation_pages.py @@ -1,15 +1,29 @@ import importlib import inspect import logging +from typing import Dict from nicegui import context, ui from . import documentation from .documentation.content.overview import Overview +from .documentation.model import ElementDocumentation, SectionDocumentation +from .documentation.sections.text_elements import TextElementsDocumentation from .header import add_head_html, add_header from .style import section_heading, side_menu -overview = Overview() +overview = Overview('/documentation') +sections: Dict[str, SectionDocumentation] = { + d.route.split('/')[-1]: d + for d in [ + TextElementsDocumentation('/documentation/section_text_elements'), + ] +} +elements: Dict[str, ElementDocumentation] = { + element_documentation.route.split('/')[-1]: element_documentation + for section in sections.values() + for element_documentation in section.element_documentations +} def create_overview() -> None: @@ -17,6 +31,13 @@ def create_overview() -> None: documentation.render(overview) +def create_page(name: str) -> None: + doc = elements.get(name) or sections.get(name) + if not doc: + raise ValueError(f'unknown documentation page: {name}') + documentation.render(doc) + + def create_section(name: str) -> None: """Create a documentation section.""" add_head_html()