Browse Source

serve dependencies for custom elements on demand

Falko Schindler 2 years ago
parent
commit
483970b8d9

+ 2 - 2
nicegui/elements/colors.py

@@ -1,12 +1,12 @@
+from ..routes import add_dependencies
 from .custom_view import CustomView
 from .element import Element
 
-CustomView.use(__file__)
-
 
 class ColorsView(CustomView):
 
     def __init__(self, primary, secondary, accent, positive, negative, info, warning):
+        add_dependencies(__file__)
         super().__init__('colors',
                          primary=primary,
                          secondary=secondary,

+ 2 - 2
nicegui/elements/custom_example.py

@@ -1,12 +1,12 @@
+from ..routes import add_dependencies
 from .custom_view import CustomView
 from .element import Element
 
-CustomView.use(__file__)
-
 
 class CustomExampleView(CustomView):
 
     def __init__(self, on_change):
+        add_dependencies(__file__)
         super().__init__('custom_example', value=0)
 
         self.on_change = on_change

+ 0 - 24
nicegui/elements/custom_view.py

@@ -1,9 +1,4 @@
-import os.path
-from typing import List
-
 import justpy as jp
-from starlette.responses import FileResponse
-from starlette.routing import Route
 
 
 class CustomView(jp.JustpyBaseComponent):
@@ -31,22 +26,3 @@ class CustomView(jp.JustpyBaseComponent):
             'style': self.style,
             'options': self.options,
         }
-
-    @staticmethod
-    def use(py_filepath: str, dependencies: List[str] = []):
-        vue_filepath = os.path.splitext(os.path.realpath(py_filepath))[0] + '.js'
-
-        for dependency in dependencies:
-            is_remote = dependency.startswith('http://') or dependency.startswith('https://')
-            src = dependency if is_remote else f'lib/{dependency}'
-            if src not in jp.component_file_list:
-                jp.component_file_list += [src]
-                if not is_remote:
-                    filepath = f'{os.path.dirname(vue_filepath)}/{src}'
-                    route = Route(f'/{src}', lambda _, filepath=filepath: FileResponse(filepath))
-                    jp.app.routes.insert(0, route)
-
-        if vue_filepath not in jp.component_file_list:
-            filename = os.path.basename(vue_filepath)
-            jp.app.routes.insert(0, Route(f'/{filename}', lambda _: FileResponse(vue_filepath)))
-            jp.component_file_list += [filename]

+ 2 - 2
nicegui/elements/interactive_image.py

@@ -6,15 +6,15 @@ from typing import Any, Callable, Dict, List, Optional
 from justpy import WebPage
 
 from ..events import MouseEventArguments, handle_event
+from ..routes import add_dependencies
 from .custom_view import CustomView
 from .element import Element
 
-CustomView.use(__file__)
-
 
 class InteractiveImageView(CustomView):
 
     def __init__(self, source: str, on_mouse: Callable, events: List[str], cross: bool):
+        add_dependencies(__file__)
         super().__init__('interactive_image', source=source, events=events, cross=cross, svg_content='')
         self.allowed_events = ['onMouse', 'onConnect']
         self.initialize(onMouse=on_mouse, onConnect=self.on_connect)

+ 2 - 2
nicegui/elements/joystick.py

@@ -1,10 +1,9 @@
 from typing import Any, Callable, Optional
 
+from ..routes import add_dependencies
 from .custom_view import CustomView
 from .element import Element
 
-CustomView.use(__file__, ['nipplejs.min.js'])
-
 
 class JoystickView(CustomView):
 
@@ -13,6 +12,7 @@ class JoystickView(CustomView):
                  on_move: Optional[Callable],
                  on_end: Optional[Callable],
                  **options: Any):
+        add_dependencies(__file__, ['nipplejs.min.js'])
         super().__init__('joystick', **options)
 
         self.on_start = on_start

+ 2 - 2
nicegui/elements/keyboard.py

@@ -2,15 +2,15 @@ import traceback
 from typing import Callable, Dict, Optional
 
 from ..events import KeyboardAction, KeyboardKey, KeyboardModifiers, KeyEventArguments, handle_event
+from ..routes import add_dependencies
 from .custom_view import CustomView
 from .element import Element
 
-CustomView.use(__file__)
-
 
 class KeyboardView(CustomView):
 
     def __init__(self, on_key: Callable, repeating: bool):
+        add_dependencies(__file__)
         super().__init__('keyboard', active_js_events=['keydown', 'keyup'], repeating=repeating)
         self.allowed_events = ['keyboardEvent']
         self.style = 'display: none'

+ 2 - 2
nicegui/elements/log.py

@@ -8,16 +8,16 @@ from typing import Deque
 
 from justpy.htmlcomponents import WebPage
 
+from ..routes import add_dependencies
 from ..task_logger import create_task
 from .custom_view import CustomView
 from .element import Element
 
-CustomView.use(__file__)
-
 
 class LogView(CustomView):
 
     def __init__(self, lines: Deque[str], max_lines: int):
+        add_dependencies(__file__)
         super().__init__('log', max_lines=max_lines)
         self.lines = lines
         self.allowed_events = ['onConnect']

+ 9 - 9
nicegui/elements/scene.py

@@ -7,21 +7,13 @@ from justpy import WebPage
 
 from ..events import handle_event
 from ..globals import view_stack
+from ..routes import add_dependencies
 from ..task_logger import create_task
 from .custom_view import CustomView
 from .element import Element
 from .page import Page
 from .scene_object3d import Object3D
 
-CustomView.use(__file__, [
-    'three.min.js',
-    'CSS2DRenderer.js',
-    'CSS3DRenderer.js',
-    'OrbitControls.js',
-    'STLLoader.js',
-    'tween.umd.min.js',
-])
-
 
 @dataclass
 class SceneCamera:
@@ -45,6 +37,14 @@ class SceneCamera:
 class SceneView(CustomView):
 
     def __init__(self, *, width: int, height: int, on_click: Optional[Callable]):
+        add_dependencies(__file__, [
+            'three.min.js',
+            'CSS2DRenderer.js',
+            'CSS3DRenderer.js',
+            'OrbitControls.js',
+            'STLLoader.js',
+            'tween.umd.min.js',
+        ])
         super().__init__('scene', width=width, height=height)
         self.on_click = on_click
         self.allowed_events = ['onConnect', 'onClick']

+ 1 - 0
nicegui/globals.py

@@ -25,6 +25,7 @@ disconnect_handlers: List[Union[Callable, Awaitable]] = []
 startup_handlers: List[Union[Callable, Awaitable]] = []
 shutdown_handlers: List[Union[Callable, Awaitable]] = []
 has_auto_index_page: bool = False
+dependencies: Dict[str, List[str]] = {}
 
 
 def find_route(function: Callable) -> str:

+ 2 - 0
nicegui/nicegui.py

@@ -11,6 +11,7 @@ if True:  # NOTE: prevent formatter from mixing up these lines
 
 from . import binding, globals
 from .elements.page import create_page_routes, init_auto_index_page
+from .routes import serve_dependencies
 from .task_logger import create_task
 from .timer import Timer
 
@@ -29,6 +30,7 @@ async def patched_justpy_startup():
 async def startup():
     init_auto_index_page()
     await create_page_routes()
+    serve_dependencies()
     globals.tasks.extend(create_task(t.coro, name=t.name) for t in Timer.prepared_coroutines)
     Timer.prepared_coroutines.clear()
     globals.tasks.extend(create_task(t, name='startup task')

+ 29 - 1
nicegui/routes.py

@@ -1,8 +1,12 @@
 import inspect
+import os.path
 from functools import wraps
+from typing import List
 
+import justpy as jp
 from starlette import requests, routing
-from starlette.routing import BaseRoute, Mount
+from starlette.responses import FileResponse
+from starlette.routing import BaseRoute, Mount, Route
 from starlette.staticfiles import StaticFiles
 
 from . import globals
@@ -53,3 +57,27 @@ def get(self, path: str):
         self.add_route(routing.Route(path, decorated))
         return decorated
     return decorator
+
+
+def add_dependencies(py_filepath: str, dependencies: List[str] = []) -> None:
+    globals.dependencies[py_filepath] = dependencies
+
+
+def serve_dependencies() -> None:
+    for py_filepath, dependencies in globals.dependencies.items():
+        vue_filepath = os.path.splitext(os.path.realpath(py_filepath))[0] + '.js'
+
+        for dependency in dependencies:
+            is_remote = dependency.startswith('http://') or dependency.startswith('https://')
+            src = dependency if is_remote else f'lib/{dependency}'
+            if src not in jp.component_file_list:
+                jp.component_file_list += [src]
+                if not is_remote:
+                    filepath = f'{os.path.dirname(vue_filepath)}/{src}'
+                    route = Route(f'/{src}', lambda _, filepath=filepath: FileResponse(filepath))
+                    jp.app.routes.insert(0, route)
+
+        if vue_filepath not in jp.component_file_list:
+            filename = os.path.basename(vue_filepath)
+            jp.app.routes.insert(0, Route(f'/{filename}', lambda _: FileResponse(vue_filepath)))
+            jp.component_file_list += [filename]