浏览代码

serve CSS and JS resources locally

Falko Schindler 1 年之前
父节点
当前提交
b465af3bb7
共有 5 个文件被更改,包括 51 次插入5 次删除
  1. 22 1
      nicegui/dependencies.py
  2. 10 1
      nicegui/element.py
  3. 4 2
      nicegui/elements/leaflet.js
  4. 3 0
      nicegui/elements/leaflet.py
  5. 12 1
      nicegui/nicegui.py

+ 22 - 1
nicegui/dependencies.py

@@ -38,6 +38,12 @@ class JsComponent(Component):
     pass
     pass
 
 
 
 
+@dataclass(**KWONLY_SLOTS)
+class Resource:
+    key: str
+    path: Path
+
+
 @dataclass(**KWONLY_SLOTS)
 @dataclass(**KWONLY_SLOTS)
 class Library:
 class Library:
     key: str
     key: str
@@ -49,6 +55,7 @@ class Library:
 vue_components: Dict[str, VueComponent] = {}
 vue_components: Dict[str, VueComponent] = {}
 js_components: Dict[str, JsComponent] = {}
 js_components: Dict[str, JsComponent] = {}
 libraries: Dict[str, Library] = {}
 libraries: Dict[str, Library] = {}
+resources: Dict[str, Resource] = {}
 
 
 
 
 def register_vue_component(path: Path) -> Component:
 def register_vue_component(path: Path) -> Component:
@@ -89,17 +96,31 @@ def register_library(path: Path, *, expose: bool = False) -> Library:
     raise ValueError(f'Unsupported library type "{path.suffix}"')
     raise ValueError(f'Unsupported library type "{path.suffix}"')
 
 
 
 
+def register_resource(path: Path) -> Resource:
+    """Register a resource."""
+    key = compute_key(path)
+    if key in resources and resources[key].path == path:
+        return resources[key]
+    assert key not in resources, f'Duplicate resource {key}'
+    resources[key] = Resource(key=key, path=path)
+    print(f'Registered resource {resources[key]}')
+    return resources[key]
+
+
 def compute_key(path: Path) -> str:
 def compute_key(path: Path) -> str:
     """Compute a key for a given path using a hash function.
     """Compute a key for a given path using a hash function.
 
 
     If the path is relative to the NiceGUI base directory, the key is computed from the relative path.
     If the path is relative to the NiceGUI base directory, the key is computed from the relative path.
     """
     """
     nicegui_base = Path(__file__).parent
     nicegui_base = Path(__file__).parent
+    is_file = path.is_file()
     try:
     try:
         path = path.relative_to(nicegui_base)
         path = path.relative_to(nicegui_base)
     except ValueError:
     except ValueError:
         pass
         pass
-    return f'{hash_file_path(path.parent)}/{path.name}'
+    if is_file:
+        return f'{hash_file_path(path.parent)}/{path.name}'
+    return f'{hash_file_path(path)}'
 
 
 
 
 def _get_name(path: Path) -> str:
 def _get_name(path: Path) -> str:

+ 10 - 1
nicegui/element.py

@@ -11,11 +11,12 @@ from typing_extensions import Self
 
 
 from . import context, core, events, json, outbox, storage
 from . import context, core, events, json, outbox, storage
 from .awaitable_response import AwaitableResponse, NullResponse
 from .awaitable_response import AwaitableResponse, NullResponse
-from .dependencies import Component, Library, register_library, register_vue_component
+from .dependencies import Component, Library, register_library, register_resource, register_vue_component
 from .elements.mixins.visibility import Visibility
 from .elements.mixins.visibility import Visibility
 from .event_listener import EventListener
 from .event_listener import EventListener
 from .slot import Slot
 from .slot import Slot
 from .tailwind import Tailwind
 from .tailwind import Tailwind
+from .version import __version__
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from .client import Client
     from .client import Client
@@ -138,6 +139,14 @@ class Element(Visibility):
         cls._default_classes = copy(cls._default_classes)
         cls._default_classes = copy(cls._default_classes)
         cls._default_style = copy(cls._default_style)
         cls._default_style = copy(cls._default_style)
 
 
+    def add_resource(self, path: Union[str, Path]) -> None:
+        """Add a resource to the element.
+
+        :param path: path to the resource (e.g. folder with CSS and JavaScript files)
+        """
+        resource = register_resource(Path(path))
+        self._props['resource_path'] = f'_nicegui/{__version__}/resources/{resource.key}'
+
     def add_slot(self, name: str, template: Optional[str] = None) -> Slot:
     def add_slot(self, name: str, template: Optional[str] = None) -> Slot:
         """Add a slot to the element.
         """Add a slot to the element.
 
 

+ 4 - 2
nicegui/elements/leaflet.js

@@ -4,11 +4,13 @@ export default {
   template: "<div></div>",
   template: "<div></div>",
   props: {
   props: {
     map_options: Object,
     map_options: Object,
+    resource_path: String,
   },
   },
   async mounted() {
   async mounted() {
+    await this.$nextTick(); // NOTE: wait for window.path_prefix to be set
     const promisses = [
     const promisses = [
-      loadResource("https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.6.0/leaflet.css"),
-      loadResource("https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.6.0/leaflet.js"),
+      loadResource(window.path_prefix + `${this.resource_path}/leaflet.css`),
+      loadResource(window.path_prefix + `${this.resource_path}/leaflet.js`),
     ];
     ];
     if (this.map_options.drawControl) {
     if (this.map_options.drawControl) {
       promisses.push(loadResource("https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"));
       promisses.push(loadResource("https://cdnjs.cloudflare.com/ajax/libs/leaflet.draw/1.0.4/leaflet.draw.css"));

+ 3 - 0
nicegui/elements/leaflet.py

@@ -1,3 +1,4 @@
+from pathlib import Path
 from typing import Any, List, Tuple, cast
 from typing import Any, List, Tuple, cast
 
 
 from .. import binding
 from .. import binding
@@ -21,6 +22,8 @@ class Leaflet(Element, component='leaflet.js'):
                  draw_control: bool = False,
                  draw_control: bool = False,
                  ) -> None:
                  ) -> None:
         super().__init__()
         super().__init__()
+        self.add_resource(Path(__file__).parent / 'lib' / 'leaflet')
+
         self.layers: List[Layer] = []
         self.layers: List[Layer] = []
 
 
         self.set_location(location)
         self.set_location(location)

+ 12 - 1
nicegui/nicegui.py

@@ -14,7 +14,7 @@ from fastapi_socketio import SocketManager
 from . import air, background_tasks, binding, core, favicon, helpers, json, outbox, run, welcome
 from . import air, background_tasks, binding, core, favicon, helpers, json, outbox, run, welcome
 from .app import App
 from .app import App
 from .client import Client
 from .client import Client
-from .dependencies import js_components, libraries
+from .dependencies import js_components, libraries, resources
 from .error import error_content
 from .error import error_content
 from .json import NiceGUIJSONResponse
 from .json import NiceGUIJSONResponse
 from .logging import log
 from .logging import log
@@ -76,6 +76,17 @@ def _get_component(key: str) -> FileResponse:
     raise HTTPException(status_code=404, detail=f'component "{key}" not found')
     raise HTTPException(status_code=404, detail=f'component "{key}" not found')
 
 
 
 
+@app.get(f'/_nicegui/{__version__}' + '/resources/{key}/{path:path}')
+def _get_resource(key: str, path: str) -> FileResponse:
+    if key in resources:
+        filepath = resources[key].path / path
+        if filepath.exists():
+            headers = {'Cache-Control': 'public, max-age=3600'}
+            media_type, _ = mimetypes.guess_type(filepath)
+            return FileResponse(filepath, media_type=media_type, headers=headers)
+    raise HTTPException(status_code=404, detail=f'resource "{key}" not found')
+
+
 async def _startup() -> None:
 async def _startup() -> None:
     """Handle the startup event."""
     """Handle the startup event."""
     if not app.config.has_run_config:
     if not app.config.has_run_config: