浏览代码

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
 
 
+@dataclass(**KWONLY_SLOTS)
+class Resource:
+    key: str
+    path: Path
+
+
 @dataclass(**KWONLY_SLOTS)
 class Library:
     key: str
@@ -49,6 +55,7 @@ class Library:
 vue_components: Dict[str, VueComponent] = {}
 js_components: Dict[str, JsComponent] = {}
 libraries: Dict[str, Library] = {}
+resources: Dict[str, Resource] = {}
 
 
 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}"')
 
 
+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:
     """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.
     """
     nicegui_base = Path(__file__).parent
+    is_file = path.is_file()
     try:
         path = path.relative_to(nicegui_base)
     except ValueError:
         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:

+ 10 - 1
nicegui/element.py

@@ -11,11 +11,12 @@ from typing_extensions import Self
 
 from . import context, core, events, json, outbox, storage
 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 .event_listener import EventListener
 from .slot import Slot
 from .tailwind import Tailwind
+from .version import __version__
 
 if TYPE_CHECKING:
     from .client import Client
@@ -138,6 +139,14 @@ class Element(Visibility):
         cls._default_classes = copy(cls._default_classes)
         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:
         """Add a slot to the element.
 

+ 4 - 2
nicegui/elements/leaflet.js

@@ -4,11 +4,13 @@ export default {
   template: "<div></div>",
   props: {
     map_options: Object,
+    resource_path: String,
   },
   async mounted() {
+    await this.$nextTick(); // NOTE: wait for window.path_prefix to be set
     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) {
       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 .. import binding
@@ -21,6 +22,8 @@ class Leaflet(Element, component='leaflet.js'):
                  draw_control: bool = False,
                  ) -> None:
         super().__init__()
+        self.add_resource(Path(__file__).parent / 'lib' / 'leaflet')
+
         self.layers: List[Layer] = []
 
         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 .app import App
 from .client import Client
-from .dependencies import js_components, libraries
+from .dependencies import js_components, libraries, resources
 from .error import error_content
 from .json import NiceGUIJSONResponse
 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')
 
 
+@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:
     """Handle the startup event."""
     if not app.config.has_run_config: