reference_tools.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import importlib
  2. import re
  3. from typing import Callable, Optional, Union
  4. import docutils.core
  5. from nicegui import globals, ui
  6. from nicegui.elements.markdown import apply_tailwind
  7. from .example import example
  8. SPECIAL_CHARACTERS = re.compile('[^(a-z)(A-Z)(0-9)-]')
  9. def remove_indentation(text: str) -> str:
  10. """Remove indentation from a multi-line string based on the indentation of the first line."""
  11. lines = text.splitlines()
  12. while lines and not lines[0].strip():
  13. lines.pop(0)
  14. if not lines:
  15. return ''
  16. indentation = len(lines[0]) - len(lines[0].lstrip())
  17. return '\n'.join(line[indentation:] for line in lines)
  18. def create_anchor_name(text: str) -> str:
  19. return SPECIAL_CHARACTERS.sub('_', text).lower()
  20. def get_menu() -> ui.left_drawer:
  21. return [element for element in globals.get_client().elements.values() if isinstance(element, ui.left_drawer)][0]
  22. def heading(text: str, *, make_menu_entry: bool = True) -> None:
  23. ui.html(f'<em>{text}</em>').classes('mt-8 text-3xl font-weight-500')
  24. if make_menu_entry:
  25. with get_menu():
  26. ui.label(text).classes('font-bold mt-4')
  27. def subheading(text: str, *, make_menu_entry: bool = True) -> None:
  28. name = create_anchor_name(text)
  29. ui.html(f'<div id="{name}"></div>').style('position: relative; top: -90px')
  30. with ui.row().classes('gap-2 items-center'):
  31. ui.label(text).classes('text-2xl')
  32. with ui.link(target=f'#{name}'):
  33. ui.icon('link', size='sm').classes('text-gray-400 hover:text-gray-800')
  34. if make_menu_entry:
  35. with get_menu() as menu:
  36. async def click():
  37. if await ui.run_javascript(f'!!document.querySelector("div.q-drawer__backdrop")'):
  38. menu.hide()
  39. ui.open(f'#{name}')
  40. ui.link(text, target=f'#{name}').props('data-close-overlay').on('click', click)
  41. def markdown(text: str) -> None:
  42. ui.markdown(remove_indentation(text))
  43. class text_example:
  44. def __init__(self, title: str, explanation: str) -> None:
  45. self.title = title
  46. self.explanation = explanation
  47. self.make_menu_entry = True
  48. def __call__(self, f: Callable) -> Callable:
  49. subheading(self.title, make_menu_entry=self.make_menu_entry)
  50. markdown(self.explanation)
  51. return example()(f)
  52. class intro_example(text_example):
  53. def __init__(self, title: str, explanation: str) -> None:
  54. super().__init__(title, explanation)
  55. self.make_menu_entry = False
  56. class element_example:
  57. def __init__(self, element_class: Union[Callable, type], browser_title: Optional[str] = None) -> None:
  58. self.element_class = element_class
  59. self.browser_title = browser_title
  60. def __call__(self, f: Callable) -> Callable:
  61. doc = self.element_class.__doc__ or self.element_class.__init__.__doc__
  62. title, documentation = doc.split('\n', 1)
  63. documentation = remove_indentation(documentation)
  64. documentation = documentation.replace('param ', '')
  65. html = docutils.core.publish_parts(documentation, writer_name='html5_polyglot')['html_body']
  66. html = apply_tailwind(html)
  67. with ui.column().classes('w-full mb-8 gap-2'):
  68. subheading(title)
  69. ui.html(html).classes('documentation bold-links arrow-links')
  70. return example(browser_title=self.browser_title)(f)
  71. def load_example(element_class: type) -> None:
  72. def pascal_to_snake(name: str) -> str:
  73. return re.sub(r'(?<!^)(?=[A-Z])', '_', name).lower()
  74. name = pascal_to_snake(element_class.__name__)
  75. module = importlib.import_module(f'website.more_reference.{name}_reference')
  76. element_example(element_class)(getattr(module, 'main_example'))