main.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. #!/usr/bin/env python3
  2. import importlib
  3. import inspect
  4. if True:
  5. # increasing max decode packets to be able to transfer images
  6. # see https://github.com/miguelgrinberg/python-engineio/issues/142
  7. from engineio.payload import Payload
  8. Payload.max_decode_packets = 500
  9. import os
  10. from pathlib import Path
  11. from typing import Optional
  12. from fastapi import Request
  13. from fastapi.responses import FileResponse, RedirectResponse
  14. from starlette.middleware.sessions import SessionMiddleware
  15. import prometheus
  16. from nicegui import Client, app
  17. from nicegui import globals as nicegui_globals
  18. from nicegui import ui
  19. from website import documentation, example_card, svg
  20. from website.demo import bash_window, browser_window, python_window
  21. from website.documentation_tools import create_anchor_name, element_demo, generate_class_doc
  22. from website.star import add_star
  23. from website.style import example_link, features, heading, link_target, section_heading, side_menu, subtitle, title
  24. prometheus.start_monitor(app)
  25. # session middleware is required for demo in documentation and prometheus
  26. app.add_middleware(SessionMiddleware, secret_key='NiceGUI is awesome!')
  27. app.add_static_files('/favicon', str(Path(__file__).parent / 'website' / 'favicon'))
  28. app.add_static_files('/fonts', str(Path(__file__).parent / 'website' / 'fonts'))
  29. @app.get('/logo.png')
  30. def logo():
  31. return FileResponse(svg.PATH / 'logo.png', media_type='image/png')
  32. @app.get('/logo_square.png')
  33. def logo():
  34. return FileResponse(svg.PATH / 'logo_square.png', media_type='image/png')
  35. @app.middleware('http')
  36. async def redirect_reference_to_documentation(request: Request, call_next):
  37. if request.url.path == '/reference':
  38. return RedirectResponse('/documentation')
  39. return await call_next(request)
  40. # NOTE in our global fly.io deployment we need to make sure that the websocket connects back to the same instance
  41. fly_instance_id = os.environ.get('FLY_ALLOC_ID', '').split('-')[0]
  42. if fly_instance_id:
  43. nicegui_globals.socket_io_js_extra_headers['fly-force-instance-id'] = fly_instance_id
  44. def add_head_html() -> None:
  45. ui.add_head_html((Path(__file__).parent / 'website' / 'static' / 'header.html').read_text())
  46. ui.add_head_html(f"<style>{(Path(__file__).parent / 'website' / 'static' / 'style.css').read_text()}</style>")
  47. def add_header(menu: Optional[ui.left_drawer] = None) -> None:
  48. menu_items = {
  49. 'Installation': '/#installation',
  50. 'Features': '/#features',
  51. 'Demos': '/#demos',
  52. 'Documentation': '/documentation',
  53. 'Examples': '/#examples',
  54. 'Why?': '/#why',
  55. }
  56. with ui.header() \
  57. .classes('items-center duration-200 p-0 px-4 no-wrap') \
  58. .style('box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1)'):
  59. if menu:
  60. ui.button(on_click=menu.toggle).props('flat color=white icon=menu round') \
  61. .classes('max-[405px]:hidden lg:hidden')
  62. with ui.link(target=index_page).classes('row gap-4 items-center no-wrap mr-auto'):
  63. svg.face().classes('w-8 stroke-white stroke-2')
  64. svg.word().classes('w-24')
  65. with ui.row().classes('max-lg:hidden'):
  66. for title, target in menu_items.items():
  67. ui.link(title, target).classes(replace='text-lg text-white')
  68. with ui.link(target='https://discord.gg/TEpFeAaF4f'):
  69. svg.discord().classes('fill-white scale-125 m-1')
  70. with ui.link(target='https://github.com/zauberzeug/nicegui/'):
  71. svg.github().classes('fill-white scale-125 m-1')
  72. add_star().classes('max-[460px]:hidden')
  73. with ui.row().classes('lg:hidden'):
  74. with ui.button().props('flat color=white icon=more_vert round'):
  75. with ui.menu().classes('bg-primary text-white text-lg').props(remove='no-parent-event'):
  76. for title, target in menu_items.items():
  77. ui.menu_item(title, on_click=lambda _, target=target: ui.open(target))
  78. @ui.page('/')
  79. async def index_page(client: Client):
  80. client.content.classes('p-0 gap-0')
  81. add_head_html()
  82. add_header()
  83. with ui.row().classes('w-full h-screen items-center gap-8 pr-4 no-wrap into-section'):
  84. svg.face(half=True).classes('stroke-black w-[200px] md:w-[230px] lg:w-[300px]')
  85. with ui.column().classes('gap-4 md:gap-8 pt-32'):
  86. title('Meet the *NiceGUI*.')
  87. subtitle('And let any browser be the frontend of your Python code.') \
  88. .classes('max-w-[20rem] sm:max-w-[24rem] md:max-w-[30rem]')
  89. ui.link(target='#about').classes('scroll-indicator')
  90. with ui.row().classes('''
  91. dark-box min-h-screen no-wrap
  92. justify-center items-center flex-col md:flex-row
  93. py-20 px-8 lg:px-16
  94. gap-8 sm:gap-16 md:gap-8 lg:gap-16
  95. '''):
  96. link_target('about')
  97. with ui.column().classes('text-white max-w-4xl'):
  98. heading('Interact with Python through buttons, dialogs, 3D&nbsp;scenes, plots and much more.')
  99. with ui.column().classes('gap-2 bold-links arrow-links text-lg'):
  100. ui.markdown(
  101. 'NiceGUI handles all the web development details for you. '
  102. 'So you can focus on writing Python code. '
  103. 'Anything from short scripts and dashboards to full robotics projects, IoT solutions, '
  104. 'smart home automations and machine learning projects can benefit from having all code in one place.'
  105. )
  106. ui.markdown(
  107. 'Available as '
  108. '[PyPI package](https://pypi.org/project/nicegui/), '
  109. '[Docker image](https://hub.docker.com/r/zauberzeug/nicegui) and on '
  110. '[GitHub](https://github.com/zauberzeug/nicegui).')
  111. example_card.create()
  112. with ui.column().classes('w-full text-lg p-8 lg:p-16 max-w-[1600px] mx-auto'):
  113. link_target('installation', '-50px')
  114. section_heading('Installation', 'Get *started*')
  115. with ui.row().classes('w-full text-lg leading-tight grid grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 gap-8'):
  116. with ui.column().classes('w-full max-w-md gap-2'):
  117. ui.html('<em>1.</em>').classes('text-3xl font-bold')
  118. ui.markdown('Create __main.py__').classes('text-lg')
  119. with python_window(classes='w-full h-52'):
  120. ui.markdown('''
  121. ```python\n
  122. from nicegui import ui
  123. ui.label('Hello NiceGUI!')
  124. ui.run()
  125. ```
  126. ''')
  127. with ui.column().classes('w-full max-w-md gap-2'):
  128. ui.html('<em>2.</em>').classes('text-3xl font-bold')
  129. ui.markdown('Install and launch').classes('text-lg')
  130. with bash_window(classes='w-full h-52'):
  131. ui.markdown('''
  132. ```bash
  133. pip3 install nicegui
  134. python3 main.py
  135. ```
  136. ''')
  137. with ui.column().classes('w-full max-w-md gap-2'):
  138. ui.html('<em>3.</em>').classes('text-3xl font-bold')
  139. ui.markdown('Enjoy!').classes('text-lg')
  140. with browser_window(classes='w-full h-52'):
  141. ui.label('Hello NiceGUI!')
  142. with ui.expansion('...or use Docker to run your main.py').classes('w-full gap-2 bold-links arrow-links'):
  143. with ui.row().classes('mt-8 w-full justify-center items-center gap-8'):
  144. ui.markdown('''
  145. With our [multi-arch Docker image](https://hub.docker.com/repository/docker/zauberzeug/nicegui)
  146. you can start the server without installing any packages.
  147. The command searches for `main.py` in in your current directory and makes the app available at http://localhost:8888.
  148. ''').classes('max-w-xl')
  149. with bash_window(classes='max-w-lg w-full h-52'):
  150. ui.markdown('''
  151. ```bash
  152. docker run -it --rm -p 8888:8080 \\
  153. -v "$PWD":/app zauberzeug/nicegui
  154. ```
  155. ''')
  156. with ui.column().classes('w-full p-8 lg:p-16 bold-links arrow-links max-w-[1600px] mx-auto'):
  157. link_target('features', '-50px')
  158. section_heading('Features', 'Code *nicely*')
  159. with ui.row().classes('w-full text-lg leading-tight grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-8'):
  160. features('swap_horiz', 'Interaction', [
  161. 'buttons, switches, sliders, inputs, ...',
  162. 'notifications, dialogs and menus',
  163. 'keyboard input',
  164. 'on-screen joystick',
  165. ])
  166. features('space_dashboard', 'Layout', [
  167. 'navigation bars, tabs, panels, ...',
  168. 'grouping with rows, columns and cards',
  169. 'HTML and Markdown elements',
  170. 'flex layout by default',
  171. ])
  172. features('insights', 'Visualization', [
  173. 'charts, diagrams and tables',
  174. '3D scenes',
  175. 'progress bars',
  176. 'built-in timer for data refresh',
  177. ])
  178. features('brush', 'Styling', [
  179. 'customizable color themes',
  180. 'custom CSS and classes',
  181. 'modern look with material design',
  182. '[Tailwind CSS](https://tailwindcss.com/) auto-completion',
  183. ])
  184. features('source', 'Coding', [
  185. 'live-cycle events',
  186. 'implicit reload on code change',
  187. 'straight-forward data binding',
  188. 'Jupyter notebook compatibility',
  189. ])
  190. features('anchor', 'Foundation', [
  191. 'generic [Vue](https://vuejs.org/) to Python bridge',
  192. 'dynamic GUI through [Quasar](https://quasar.dev/)',
  193. 'content is served with [FastAPI](http://fastapi.tiangolo.com/)',
  194. 'Python 3.7+',
  195. ])
  196. with ui.column().classes('w-full p-8 lg:p-16 max-w-[1600px] mx-auto'):
  197. link_target('demos', '-50px')
  198. section_heading('Demos', 'Try *this*')
  199. with ui.column().classes('w-full'):
  200. documentation.create_intro()
  201. with ui.column().classes('dark-box p-8 lg:p-16 my-16'):
  202. with ui.column().classes('mx-auto items-center gap-y-8 gap-x-32 lg:flex-row'):
  203. with ui.column().classes('gap-1 max-lg:items-center max-lg:text-center'):
  204. ui.markdown('Browse through plenty of live demos.') \
  205. .classes('text-white text-2xl md:text-3xl font-medium')
  206. ui.html('Fun-Fact: This whole website is also coded with NiceGUI.') \
  207. .classes('text-white text-lg md:text-xl')
  208. ui.link('Documentation', '/documentation') \
  209. .classes('rounded-full mx-auto px-12 py-2 text-white bg-white font-medium text-lg md:text-xl')
  210. with ui.column().classes('w-full p-8 lg:p-16 max-w-[1600px] mx-auto'):
  211. link_target('examples', '-50px')
  212. section_heading('In-depth examples', 'Pick your *solution*')
  213. with ui.row().classes('w-full text-lg leading-tight grid grid-cols-1 sm:grid-cols-2 xl:grid-cols-3 gap-4'):
  214. example_link('Slideshow', 'implements a keyboard-controlled image slideshow')
  215. example_link('Authentication', 'shows how to use sessions to build a login screen')
  216. example_link('Modularization',
  217. 'provides an example of how to modularize your application into multiple files and reuse code')
  218. example_link('FastAPI',
  219. 'illustrates the integration of NiceGUI with an existing FastAPI application')
  220. example_link('Map',
  221. 'demonstrates wrapping the JavaScript library [leaflet](https://leafletjs.com/) '
  222. 'to display a map at specific locations')
  223. example_link('AI Interface',
  224. 'utilizes the [replicate](https://replicate.com) library to perform voice-to-text '
  225. 'transcription and generate images from prompts with Stable Diffusion')
  226. example_link('3D Scene', 'creates a webGL view and loads an STL mesh illuminated with a spotlight')
  227. example_link('Custom Vue Component', 'shows how to write and integrate a custom Vue component')
  228. example_link('Image Mask Overlay', 'shows how to overlay an image with a mask')
  229. example_link('Infinite Scroll', 'presents an infinitely scrolling image gallery')
  230. example_link('OpenCV Webcam', 'uses OpenCV to capture images from a webcam')
  231. example_link('SVG Clock', 'displays an analog clock by updating an SVG with `ui.timer`')
  232. example_link('Progress', 'demonstrates a progress bar for heavy computations')
  233. example_link('NGINX Subpath', 'shows the setup to serve an app behind a reverse proxy subpath')
  234. example_link('Script Executor', 'executes scripts on selection and displays the output')
  235. example_link('Local File Picker', 'demonstrates a dialog for selecting files locally on the server')
  236. example_link('Search as you type', 'using public API of thecocktaildb.com to search for cocktails')
  237. example_link('Menu and Tabs', 'uses Quasar to create foldable menu and tabs inside a header bar')
  238. example_link('Trello Cards', 'shows Trello-like cards that can be dragged and dropped into columns')
  239. example_link('Slots', 'shows how to use scoped slots to customize Quasar elements')
  240. example_link('Table and slots', 'shows how to use component slots in a table')
  241. example_link('Single Page App', 'navigate without reloading the page')
  242. example_link('Chat App', 'a simple chat app')
  243. example_link('Pandas DataFrame', 'shows how to display an editable [pandas](https://pandas.pydata.org) DataFrame')
  244. with ui.row().classes('bg-primary w-full min-h-screen mt-16'):
  245. link_target('why')
  246. with ui.column().classes('''
  247. max-w-[1600px] m-auto
  248. py-20 px-8 lg:px-16
  249. items-center justify-center no-wrap flex-col md:flex-row gap-16
  250. '''):
  251. with ui.column().classes('gap-8'):
  252. heading('Why?')
  253. with ui.column().classes('gap-2 text-xl text-white bold-links arrow-links'):
  254. ui.markdown(
  255. 'We at '
  256. '[Zauberzeug](https://zauberzeug.com) '
  257. 'like '
  258. '[Streamlit](https://streamlit.io/) '
  259. 'but find it does '
  260. '[too much magic](https://github.com/zauberzeug/nicegui/issues/1#issuecomment-847413651) '
  261. 'when it comes to state handling. '
  262. 'In search for an alternative nice library to write simple graphical user interfaces in Python we discovered '
  263. '[JustPy](https://justpy.io/). '
  264. 'Although we liked the approach, it is too "low-level HTML" for our daily usage. '
  265. 'But it inspired us to use '
  266. '[Vue](https://vuejs.org/) '
  267. 'and '
  268. '[Quasar](https://quasar.dev/) '
  269. 'for the frontend.')
  270. ui.markdown(
  271. 'We have built on top of '
  272. '[FastAPI](https://fastapi.tiangolo.com/), '
  273. 'which itself is based on the ASGI framework '
  274. '[Starlette](https://www.starlette.io/) '
  275. 'and the ASGI webserver '
  276. '[Uvicorn](https://www.uvicorn.org/) '
  277. 'because of their great performance and ease of use.'
  278. )
  279. svg.face().classes('stroke-white shrink-0 w-[200px] md:w-[300px] lg:w-[450px]')
  280. @ui.page('/documentation')
  281. def documentation_page():
  282. add_head_html()
  283. menu = side_menu()
  284. add_header(menu)
  285. ui.add_head_html('<style>html {scroll-behavior: auto;}</style>')
  286. with ui.column().classes('w-full p-8 lg:p-16 max-w-[1250px] mx-auto'):
  287. section_heading('Reference, Demos and more', '*NiceGUI* Documentation')
  288. ui.markdown('''
  289. This is the documentation for NiceGUI >= 1.0.
  290. Documentation for older versions can be found at [https://0.9.nicegui.io/](https://0.9.nicegui.io/reference).
  291. ''').classes('bold-links arrow-links')
  292. documentation.create_full()
  293. @ui.page('/documentation/{name}')
  294. def documentation_page_more(name: str):
  295. if not hasattr(ui, name):
  296. name = name.replace('_', '') # NOTE: "AG Grid" leads to anchor name "ag_grid", but class is `ui.aggrid`
  297. module = importlib.import_module(f'website.more_documentation.{name}_documentation')
  298. api = getattr(ui, name)
  299. more = getattr(module, 'more', None)
  300. back_link_target = str(api.__doc__ or api.__init__.__doc__).splitlines()[0].strip()
  301. add_head_html()
  302. add_header()
  303. with side_menu() as menu:
  304. ui.markdown(f'[← back](/documentation#{create_anchor_name(back_link_target)})').classes('bold-links')
  305. with ui.column().classes('w-full p-8 lg:p-16 max-w-[1250px] mx-auto'):
  306. section_heading('Documentation', f'ui.*{name}*')
  307. with menu:
  308. ui.markdown('**Demos**' if more else '**Demo**').classes('mt-4')
  309. element_demo(api)(getattr(module, 'main_demo'))
  310. if more:
  311. more()
  312. if inspect.isclass(api):
  313. with menu:
  314. ui.markdown('**Reference**').classes('mt-4')
  315. ui.markdown('## Reference').classes('mt-16')
  316. generate_class_doc(api)
  317. ui.run(uvicorn_reload_includes='*.py, *.css, *.html')