Bladeren bron

Merge branch 'path_improvements' into on-air

Rodja Trappe 1 jaar geleden
bovenliggende
commit
32187866dc

+ 50 - 33
nicegui/dependencies.py

@@ -1,4 +1,5 @@
 import json
+import logging
 from pathlib import Path
 from typing import Any, Dict, List, Set, Tuple
 
@@ -12,14 +13,22 @@ js_components: Dict[str, Any] = {}
 libraries: Dict[str, Any] = {}
 
 
-def register_vue_component(name: str, path: Path) -> None:
+def register_vue_component(location: Path,
+                           base_path: Path = Path(__file__).parent / 'elements', *, expose: bool = False
+                           ) -> str:
     """Register a .vue or .js Vue component.
 
-    :param name: unique machine-name (used in element's `use_library`): no space, no special characters
-    :param path: local path
+    :param location: the location to the library you want to register relative to the base_path. This is also used as the resource identifier and must therefore be url-safe.
+    :param base_path: the base path where your libraries are located
+    :return: the resource identifier library name to be used in element's `use_component`        
     """
-    suffix = path.suffix.lower()
+    if isinstance(location, str):
+        logging.warning('register_vue_component: location is a string, did you mean to use register_library?')
+        return
+    suffix = location.suffix.lower()
     assert suffix in {'.vue', '.js', '.mjs'}, 'Only VUE and JS components are supported.'
+    name = location.stem
+    path = base_path / location
     if suffix == '.vue':
         assert name not in vue_components, f'Duplicate VUE component name {name}'
         # The component (in case of .vue) is built right away to:
@@ -28,18 +37,27 @@ def register_vue_component(name: str, path: Path) -> None:
         vue_components[name] = vbuild.VBuild(name, path.read_text())
     elif suffix == '.js':
         assert name not in js_components, f'Duplicate JS component name {name}'
-        js_components[name] = {'name': name, 'path': path}
+        js_components[str(location)] = {'name': name, 'path': path}
+    return str(location)
 
 
-def register_library(name: str, path: Path, *, expose: bool = False) -> None:
+def register_library(location: Path,
+                     base_path: Path = Path(__file__).parent / 'elements' / 'lib', *, expose: bool = False
+                     ) -> str:
     """Register a new external library.
 
-    :param name: unique machine-name (used in element's `use_library`): no space, no special characters
-    :param path: local path
+    :param location: the location to the library you want to register relative to the base_path. This is also used as the resource identifier and must therefore be url-safe.
+    :param base_path: the base path where your libraries are located
     :param expose: if True, this will be exposed as an ESM module but NOT imported
+    :return: the resource identifier library name to be used in element's `use_library`
     """
-    assert path.suffix == '.js' or path.suffix == '.mjs', 'Only JS dependencies are supported.'
-    libraries[name] = {'name': name, 'path': path, 'expose': expose}
+    if isinstance(location, str):
+        return
+    assert location.suffix == '.js' or location.suffix == '.mjs', 'Only JS dependencies are supported.'
+    name = str(location)
+    assert name not in libraries, f'Duplicate js library name {name}'
+    libraries[name] = {'name': name, 'path': base_path / location,  'expose': expose}
+    return name
 
 
 def generate_resources(prefix: str, elements: List[Element]) -> Tuple[str, str, str, str, str]:
@@ -52,34 +70,33 @@ def generate_resources(prefix: str, elements: List[Element]) -> Tuple[str, str,
     import_maps = {'imports': {}}
 
     # Build the importmap structure for exposed libraries.
-    for key in libraries:
-        if key not in done_libraries and libraries[key]['expose']:
-            name = libraries[key]['name']
-            import_maps['imports'][name] = f'{prefix}/_nicegui/{__version__}/library/{key}/include'
-            done_libraries.add(key)
+    for resource in libraries:
+        if resource not in done_libraries and libraries[resource]['expose']:
+            name = libraries[resource]['name']
+            import_maps['imports'][name] = f'{prefix}/_nicegui/{__version__}/library/{resource}'
+            done_libraries.add(resource)
     # Build the none optimized component (ie, the vue component).
-    for key in vue_components:
-        if key not in done_components:
-            vue_html += f'{vue_components[key].html}\n'
-            vue_scripts += f'{vue_components[key].script.replace("Vue.component", "app.component", 1)}\n'
-            vue_styles += f'{vue_components[key].style}\n'
-            done_components.add(key)
+    for resource in vue_components:
+        if resource not in done_components:
+            vue_html += f'{vue_components[resource].html}\n'
+            vue_scripts += f'{vue_components[resource].script.replace("Vue.component", "app.component", 1)}\n'
+            vue_styles += f'{vue_components[resource].style}\n'
+            done_components.add(resource)
 
     # Build the resources associated with the elements.
     for element in elements:
-        for key in element.libraries:
-            if key in libraries and key not in done_libraries:
-                if not libraries[key]['expose']:
-                    js_imports += f'import "{prefix}/_nicegui/{__version__}/library/{key}/include";\n'
-                done_libraries.add(key)
-        for key in element.components:
-            if key in js_components and key not in done_components:
-                name = js_components[key]['name']
-                var = key.replace('-', '_')
-                js_imports += f'import {{ default as {var} }} from "{prefix}/_nicegui/{__version__}/components/{key}";\n'
+        for resource in element.libraries:
+            if resource in libraries and resource not in done_libraries:
+                if not libraries[resource]['expose']:
+                    js_imports += f'import "{prefix}/_nicegui/{__version__}/library/{resource}";\n'
+                done_libraries.add(resource)
+        for resource in element.components:
+            if resource in js_components and resource not in done_components:
+                name = js_components[resource]['name']
+                var = name.replace('-', '_')
+                js_imports += f'import {{ default as {var} }} from "{prefix}/_nicegui/{__version__}/components/{resource}";\n'
                 js_imports += f'app.component("{name}", {var});\n'
-                done_components.add(key)
-
+                done_components.add(resource)
     vue_styles = f'<style>{vue_styles}</style>'
     import_maps = f'<script type="importmap">{json.dumps(import_maps)}</script>'
     return vue_html, vue_styles, vue_scripts, import_maps, js_imports

+ 2 - 2
nicegui/elements/aggrid.py

@@ -8,7 +8,7 @@ from ..element import Element
 from ..functions.javascript import run_javascript
 
 register_vue_component('aggrid', Path(__file__).parent / 'aggrid.js')
-register_library('aggrid', Path(__file__).parent / 'lib' / 'aggrid' / 'ag-grid-community.min.js')
+library_name = register_library(Path('aggrid') / 'ag-grid-community.min.js')
 
 
 class AgGrid(Element):
@@ -29,7 +29,7 @@ class AgGrid(Element):
         self._props['html_columns'] = html_columns
         self._classes = ['nicegui-aggrid', f'ag-theme-{theme}']
         self.use_component('aggrid')
-        self.use_library('aggrid')
+        self.use_library(library_name)
 
     @staticmethod
     def from_pandas(df: 'pandas.DataFrame', *, theme: str = 'balham') -> AgGrid:

+ 7 - 7
nicegui/elements/chart.py

@@ -7,11 +7,11 @@ from ..element import Element
 register_vue_component('chart', Path(__file__).parent / 'chart.js')
 
 core_dependencies: List[Path] = []
-for path in sorted((Path(__file__).parent / 'lib' / 'highcharts').glob('*.js'), key=lambda p: p.stem):
-    register_library(path.stem, path)
-    core_dependencies.append(path)
-for path in sorted((Path(__file__).parent / 'lib' / 'highcharts' / 'modules').glob('*.js'), key=lambda p: p.stem):
-    register_library(path.stem, path)
+base = Path(__file__).parent / 'lib'
+for path in sorted((base / 'highcharts').glob('*.js'), key=lambda p: p.stem):
+    core_dependencies.append(register_library(path.relative_to(base)))
+for path in sorted((base / 'highcharts' / 'modules').glob('*.js'), key=lambda p: p.stem):
+    register_library(path.relative_to(base))
 
 
 class Chart(Element):
@@ -36,9 +36,9 @@ class Chart(Element):
         self._props['extras'] = extras
         self.use_component('chart')
         for dependency in core_dependencies:
-            self.use_library(dependency.stem)
+            self.use_library(dependency)
         for extra in extras:
-            self.use_library(extra)
+            self.use_library(f'highcharts/modules/{extra}.js')
 
     @property
     def options(self) -> Dict:

+ 2 - 2
nicegui/elements/joystick.py

@@ -6,7 +6,7 @@ from ..element import Element
 from ..events import GenericEventArguments, JoystickEventArguments, handle_event
 
 register_vue_component('joystick', Path(__file__).parent / 'joystick.vue')
-register_library('nipplejs', Path(__file__).parent / 'lib' / 'nipplejs' / 'nipplejs.js')
+library_name = register_library(Path('nipplejs') / 'nipplejs.js')
 
 
 class Joystick(Element):
@@ -28,7 +28,7 @@ class Joystick(Element):
         :param options: arguments like `color` which should be passed to the `underlying nipple.js library <https://github.com/yoannmoinet/nipplejs#options>`_
         """
         super().__init__('joystick')
-        self.use_library('nipplejs')
+        self.use_library(library_name)
         self._props['options'] = options
         self.active = False
 

+ 1 - 1
nicegui/elements/mermaid.js

@@ -1,4 +1,4 @@
-import mermaid from "mermaid";
+import mermaid from "mermaid/mermaid.esm.min.mjs";
 export default {
   template: `<div></div>`,
   mounted() {

+ 7 - 4
nicegui/elements/mermaid.py

@@ -3,8 +3,11 @@ from pathlib import Path
 from ..dependencies import register_library, register_vue_component
 from .mixins.content_element import ContentElement
 
-register_vue_component('mermaid', Path(__file__).parent / 'mermaid.js')
-register_library('mermaid', Path(__file__).parent / 'lib' / 'mermaid' / 'mermaid.esm.min.mjs', expose=True)
+component_name = register_vue_component(Path('mermaid.js'))
+library_name = register_library(Path('mermaid') / 'mermaid.esm.min.mjs', expose=True)
+extras_path = Path(__file__).parent / 'lib' / 'mermaid'
+for path in extras_path.glob('*.js'):
+    register_library(path.relative_to(extras_path.parent))
 
 
 class Mermaid(ContentElement):
@@ -19,8 +22,8 @@ class Mermaid(ContentElement):
         :param content: the Mermaid content to be displayed
         '''
         super().__init__(tag='mermaid', content=content)
-        self.use_component('mermaid')
-        self.use_library('mermaid')
+        self.use_component(component_name)
+        self.use_library(library_name)
 
     def on_content_change(self, content: str) -> None:
         self._props[self.CONTENT_PROP] = content.strip()

+ 2 - 2
nicegui/elements/plotly.py

@@ -7,7 +7,7 @@ from ..dependencies import register_library, register_vue_component
 from ..element import Element
 
 register_vue_component('plotly', Path(__file__).parent / 'plotly.vue')
-register_library('plotly', Path(__file__).parent / 'lib' / 'plotly' / 'plotly.min.js')
+library_name = register_library(Path('plotly') / 'plotly.min.js')
 
 
 class Plotly(Element):
@@ -28,7 +28,7 @@ class Plotly(Element):
                        a `dict` object with keys `data`, `layout`, `config` (optional).
         """
         super().__init__('plotly')
-        self.use_library('plotly')
+        self.use_library(library_name)
 
         self.figure = figure
         self.update()

+ 11 - 12
nicegui/elements/scene.py

@@ -10,12 +10,15 @@ from ..helpers import KWONLY_SLOTS
 from .scene_object3d import Object3D
 
 register_vue_component('scene', Path(__file__).parent / 'scene.js')
-register_library('three', Path(__file__).parent / 'lib' / 'three' / 'three.module.js', expose=True)
-register_library('CSS2DRenderer', Path(__file__).parent / 'lib' / 'three' / 'modules' / 'CSS2DRenderer.js', expose=True)
-register_library('CSS3DRenderer', Path(__file__).parent / 'lib' / 'three' / 'modules' / 'CSS3DRenderer.js', expose=True)
-register_library('OrbitControls', Path(__file__).parent / 'lib' / 'three' / 'modules' / 'OrbitControls.js', expose=True)
-register_library('STLLoader', Path(__file__).parent / 'lib' / 'three' / 'modules' / 'STLLoader.js', expose=True)
-register_library('tween', Path(__file__).parent / 'lib' / 'tween' / 'tween.umd.js')
+lib = Path('three')
+library_names = [
+    register_library(lib / 'three.module.js', expose=True),
+    register_library(lib / 'modules' / 'CSS2DRenderer.js', expose=True),
+    register_library(lib / 'modules' / 'CSS3DRenderer.js', expose=True),
+    register_library(lib / 'modules' / 'OrbitControls.js', expose=True),
+    register_library(lib / 'modules' / 'STLLoader.js', expose=True),
+    register_library(lib / 'tween' / 'tween.umd.js'),
+]
 
 
 @dataclass(**KWONLY_SLOTS)
@@ -83,12 +86,8 @@ class Scene(Element):
         self.on('init', self.handle_init)
         self.on('click3d', self.handle_click)
         self.use_component('scene')
-        self.use_library('three')
-        self.use_library('CSS2DRenderer')
-        self.use_library('CSS3DRenderer')
-        self.use_library('OrbitControls')
-        self.use_library('STLLoader')
-        self.use_library('tween')
+        for library_name in library_names:
+            self.use_library(library_name)
 
     def handle_init(self, e: GenericEventArguments) -> None:
         self.is_initialized = True

+ 16 - 11
nicegui/nicegui.py

@@ -44,21 +44,26 @@ def index(request: Request) -> Response:
     return globals.index_client.build_response(request)
 
 
-@app.get(f'/_nicegui/{__version__}' + '/library/{name}/{file}')
-def get_dependencies(name: str, file: str):
+@app.get(f'/_nicegui/{__version__}' + '/library/{name:path}')
+def get_dependencies(name: str):
     if name in libraries and libraries[name]['path'].exists():
-        filepath = Path(libraries[name]['path']).parent / file
-        if filepath.exists() and not filepath.is_dir():
-            return FileResponse(filepath, media_type='text/javascript')
-        return FileResponse(libraries[name]['path'], media_type='text/javascript')
+        return FileResponse(
+            libraries[name]['path'],
+            media_type='text/javascript',
+            headers={'Cache-Control': 'public, max-age=3600'}
+        )
     raise HTTPException(status_code=404, detail=f'dependency "{name}" not found')
 
 
-@app.get(f'/_nicegui/{__version__}' + '/components/{name}')
-def get_components(name: str):
-    if name in js_components and js_components[name]['path'].exists():
-        return FileResponse(js_components[name]['path'], media_type='text/javascript')
-    raise HTTPException(status_code=404, detail=f'library "{name}" not found')
+@app.get(f'/_nicegui/{__version__}' + '/components/{resource:path}')
+def get_components(resource: str):
+    if resource in js_components and js_components[resource]['path'].exists():
+        return FileResponse(
+            js_components[resource]['path'],
+            media_type='text/javascript',
+            headers={'Cache-Control': 'public, max-age=3600'},
+        )
+    raise HTTPException(status_code=404, detail=f'library "{resource}" not found')
 
 
 @app.on_event('startup')

+ 1 - 2
nicegui/templates/index.html

@@ -19,7 +19,6 @@
     <script src="{{ prefix | safe }}/_nicegui/{{version}}/static/vue.global.prod.js"></script>
     <script src="{{ prefix | safe }}/_nicegui/{{version}}/static/quasar.umd.prod.js"></script>
     <script src="{{ prefix | safe }}/_nicegui/{{version}}/static/quasar.{{ language }}.umd.prod.js"></script>
-    {{ import_maps | safe }}
 
     <!-- NOTE: force Prettier to keep the line break -->
     {{ body_html | safe }}
@@ -190,7 +189,7 @@
       async function loadDependencies(element) {
         for (const name of element['libraries']) {
           if (loaded_libraries.has(name)) continue;
-          await import(`{{ prefix | safe }}/_nicegui/{{version}}/library/${name}/include`);
+          await import(`{{ prefix | safe }}/_nicegui/{{version}}/library/${name}`);
           loaded_libraries.add(name);
         }
         for (const name of element['components']) {

File diff suppressed because it is too large
+ 354 - 297
poetry.lock


+ 4 - 4
pyproject.toml

@@ -12,16 +12,16 @@ keywords = ["gui", "ui", "web", "interface", "live"]
 python = "^3.7"
 typing-extensions = ">=3.10.0"
 markdown2 = "^2.4.7"
-Pygments = "^2.9.0"
-uvicorn = {extras = ["standard"], version = "^0.20.0"}
+Pygments = ">=2.9.0,<3.0.0"
+uvicorn = {extras = ["standard"], version = "^0.22.0"}
 matplotlib = [
     { version = "^3.5.0", markers = "python_version ~= '3.7'"},
-    { version = "^3.6.0", markers = "python_version ~= '3.11.0'"},
+    { version = ">=3.6.0,<4.0.0", markers = "python_version ~= '3.11.0'"},
 ]
 fastapi = ">=0.92,<1.0.0"
 fastapi-socketio = "^0.0.10"
 vbuild = "^0.8.1"
-watchfiles = "^0.18.1"
+watchfiles = ">=0.18.1,<1.0.0"
 jinja2 = "^3.1.2"
 python-multipart = "^0.0.6"
 plotly = "^5.13.0"

Some files were not shown because too many files changed in this diff