nice_gui.py 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. #!/usr/bin/env python3
  2. import traceback
  3. import justpy as jp
  4. from starlette.applications import Starlette
  5. import uvicorn
  6. import inspect
  7. import time
  8. import asyncio
  9. from contextlib import contextmanager
  10. from matplotlib import pyplot as plt
  11. from utils import handle_exceptions, provide_arguments
  12. import icecream
  13. icecream.install()
  14. wp = jp.WebPage(delete_flag=False, title='Nice GUI', favicon='favicon.png')
  15. main = jp.Div(a=wp, classes='m-4 flex flex-col items-start gap-4')
  16. main.add_page(wp)
  17. jp.justpy(lambda: wp, start_server=False)
  18. view_stack = [main]
  19. class Element:
  20. def __init__(self, view: jp.HTMLBaseComponent):
  21. view_stack[-1].add(view)
  22. view.add_page(wp)
  23. self.view = view
  24. @property
  25. def text(self):
  26. return self.view.text
  27. @text.setter
  28. def text(self, text):
  29. self.view.text = text
  30. def set_text(self, text):
  31. self.view.text = text
  32. def __enter__(self):
  33. view_stack.append(self.view)
  34. def __exit__(self, *_):
  35. view_stack.pop()
  36. class Plot(Element):
  37. def update(self, close=True):
  38. self.view.set_figure(plt.gcf())
  39. if close:
  40. plt.close()
  41. class Ui(Starlette):
  42. def label(self, text=''):
  43. view = jp.Div(text=text)
  44. return Element(view)
  45. def button(self, text, on_click=None):
  46. view = jp.Button(text=text, classes='bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded')
  47. if on_click is not None:
  48. view.on('click', handle_exceptions(provide_arguments(on_click)))
  49. return Element(view)
  50. @contextmanager
  51. def plot(self, close=True):
  52. view = jp.Matplotlib()
  53. yield Plot(view)
  54. view.set_figure(plt.gcf())
  55. if close:
  56. plt.close()
  57. def row(self):
  58. view = jp.Div(classes='flex flex-row gap-4 items-start')
  59. return Element(view)
  60. def column(self):
  61. view = jp.Div(classes='flex flex-col gap-4 items-start')
  62. return Element(view)
  63. def card(self):
  64. view = jp.Div(classes='flex flex-col gap-4 items-start p-4 rounded shadow-lg')
  65. return Element(view)
  66. def timer(self, interval, callback, *, once=False):
  67. parent = view_stack[-1]
  68. async def timeout():
  69. await asyncio.sleep(interval)
  70. handle_exceptions(callback)()
  71. await main.update()
  72. async def loop():
  73. while True:
  74. try:
  75. start = time.time()
  76. handle_exceptions(callback)()
  77. await main.update()
  78. dt = time.time() - start
  79. await asyncio.sleep(interval - dt)
  80. except:
  81. traceback.print_exc()
  82. await asyncio.sleep(interval)
  83. jp.run_task(timeout() if once else loop())
  84. def run(self):
  85. # NOTE: prevent reloader from restarting uvicorn
  86. if inspect.stack()[-2].filename.endswith('spawn.py'):
  87. return
  88. uvicorn.run('nice_gui:ui', host='0.0.0.0', port=80, lifespan='on', reload=True)
  89. # NOTE: instantiate our own ui object with all capabilities of jp.app
  90. ui = Ui()
  91. ui.__dict__.update(jp.app.__dict__)