example.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. import inspect
  2. import re
  3. from typing import Callable, Union
  4. import docutils.core
  5. from nicegui import ui
  6. from nicegui.elements.markdown import apply_tailwind
  7. REGEX_H4 = re.compile(r'<h4.*?>(.*?)</h4>')
  8. SPECIAL_CHARACTERS = re.compile('[^(a-z)(A-Z)(0-9)-]')
  9. class example:
  10. def __init__(self, content: Union[Callable, type, str], tight: bool = False) -> None:
  11. self.content = content
  12. self.markdown_classes = f'mr-8 w-full flex-none lg:w-{48 if tight else 80} xl:w-80'
  13. self.rendering_classes = f'w-{48 if tight else 64} flex-none lg:mt-12'
  14. self.source_classes = f'w-80 flex-grow overflow-auto lg:mt-12'
  15. def __call__(self, f: Callable) -> Callable:
  16. with ui.row().classes('mb-2 flex w-full'):
  17. if isinstance(self.content, str):
  18. _add_html_anchor(ui.markdown(self.content).classes(self.markdown_classes))
  19. else:
  20. doc = self.content.__doc__ or self.content.__init__.__doc__
  21. html: str = docutils.core.publish_parts(doc, writer_name='html')['html_body']
  22. html = html.replace('<p>', '<h4>', 1)
  23. html = html.replace('</p>', '</h4>', 1)
  24. html = apply_tailwind(html)
  25. _add_html_anchor(ui.html(html).classes(self.markdown_classes))
  26. with browser_window().classes(self.rendering_classes):
  27. f()
  28. code = inspect.getsource(f).splitlines()
  29. while not code[0].startswith(' ' * 8):
  30. del code[0]
  31. code = [l[8:] for l in code]
  32. while code[0].startswith('global '):
  33. del code[0]
  34. code.insert(0, '```python')
  35. code.insert(1, 'from nicegui import ui')
  36. if code[2].split()[0] not in ['from', 'import']:
  37. code.insert(2, '')
  38. for l, line in enumerate(code):
  39. if line.startswith('# ui.'):
  40. code[l] = line[2:]
  41. if line.startswith('# ui.run('):
  42. break
  43. else:
  44. code.append('')
  45. code.append('ui.run()')
  46. code.append('```')
  47. code = '\n'.join(code)
  48. with python_window().classes(self.source_classes):
  49. ui.markdown(code)
  50. return f
  51. def _add_html_anchor(element: ui.html) -> None:
  52. html = element.content
  53. match = REGEX_H4.search(html)
  54. if not match:
  55. return
  56. headline = match.groups()[0].strip()
  57. headline_id = SPECIAL_CHARACTERS.sub('_', headline).lower()
  58. if not headline_id:
  59. return
  60. icon = '<span class="material-icons">link</span>'
  61. anchor = f'<a href="reference#{headline_id}" class="text-gray-300 hover:text-black">{icon}</a>'
  62. html = html.replace('<h4', f'<h4 id="{headline_id}"', 1)
  63. html = html.replace('</h4>', f' {anchor}</h4>', 1)
  64. element.content = html
  65. def _add_dots() -> None:
  66. with ui.row().classes('gap-1').style('transform: translate(-6px, -6px)'):
  67. ui.icon('circle').style('font-size: 75%').classes('text-red-400')
  68. ui.icon('circle').style('font-size: 75%').classes('text-yellow-400')
  69. ui.icon('circle').style('font-size: 75%').classes('text-green-400')
  70. def window(color: str) -> ui.card:
  71. with ui.card().style(f'box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); background: {color}') as card:
  72. _add_dots()
  73. return card
  74. def python_window() -> ui.card:
  75. return window('#eff5ff')
  76. def browser_window() -> ui.card:
  77. with ui.card().style(f'box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1); background: white') as card:
  78. with ui.row():
  79. ui.icon('language').classes('text-blue-400').style('font-size: 90%; margin: -4px 0px 0px -4px')
  80. ui.label('localhost:8080').classes('text-blue-200').style('font-size: 60%; margin: -3px 0px 0px -12px')
  81. return card
  82. def bash_window() -> ui.card:
  83. return window('#e8e8e8')