Pārlūkot izejas kodu

refactor and improve vue dependency routing; update example

Falko Schindler 2 gadi atpakaļ
vecāks
revīzija
e24033e3e6

+ 15 - 15
examples/custom_vue_component/counter.js

@@ -1,23 +1,23 @@
-Vue.component("counter", {
+export default {
   template: `
-  <button v-bind:id="jp_props.id" v-on:click="handle_click">
-    <strong>{{jp_props.options.title}}: {{jp_props.options.value}}</strong>
+  <button @click="handle_click">
+    <strong>{{title}}: {{value}}</strong>
   </button>`,
+  data() {
+    return {
+      value: 0,
+    };
+  },
   methods: {
     handle_click() {
-      this.$props.jp_props.options.value += 1;
-      const event = {
-        event_type: "onChange",
-        vue_type: this.$props.jp_props.vue_type,
-        id: this.$props.jp_props.id,
-        page_id: page_id,
-        websocket_id: websocket_id,
-        value: this.$props.jp_props.options.value,
-      };
-      send_to_server(event, "event");
+      this.value += 1;
+      this.$emit("change", this.value);
+    },
+    reset() {
+      this.value = 0;
     },
   },
   props: {
-    jp_props: Object,
+    title: String,
   },
-});
+};

+ 7 - 23
examples/custom_vue_component/counter.py

@@ -1,33 +1,17 @@
 from typing import Callable, Optional
 
-from addict import Dict
-from nicegui.elements.custom_view import CustomView
-from nicegui.elements.element import Element
-from nicegui.routes import add_dependencies
+from nicegui.element import Element
+from nicegui.vue import register_component
 
-add_dependencies(__file__)  # automatically serve the .js file with the same name
-
-
-class CounterView(CustomView):
-
-    def __init__(self, title: str, on_change: Optional[Callable]) -> None:
-        super().__init__('counter', title=title, value=0)  # pass props to the Vue component
-
-        self.on_change = on_change
-        self.allowed_events = ['onChange']
-        self.initialize(temp=False, onChange=self.handle_change)
-
-    def handle_change(self, msg: Dict) -> None:
-        if self.on_change is not None:
-            self.on_change(msg.value)
-        return False  # avoid JustPy's page update
+register_component('counter', __file__, 'counter.js')
 
 
 class Counter(Element):
 
     def __init__(self, title: str, *, on_change: Optional[Callable] = None) -> None:
-        super().__init__(CounterView(title, on_change))
+        super().__init__('counter')
+        self._props['title'] = title
+        self.on('change', on_change)
 
     def reset(self) -> None:
-        self.view.options.value = 0
-        self.update()  # update the view after changing the counter value
+        self.run_method('reset')

+ 5 - 7
examples/custom_vue_component/main.py

@@ -1,19 +1,17 @@
 #!/usr/bin/env python3
-from nicegui import ui
-
 from counter import Counter
 
+from nicegui import ui
+
 ui.markdown('''
 #### Try the new click counter!
 
 Click to increment its value.
 ''')
 with ui.card():
-    counter = Counter('Clicks', on_change=lambda value: message.set_text(f'The value changed to {value}.'))
-
+    counter = Counter('Clicks', on_change=lambda msg: ui.notify(f'The value changed to {msg["args"]}.'))
 
-ui.button('Reset', on_click=lambda: counter.reset()).props('small outline')
 
-message = ui.label()
+ui.button('Reset', on_click=counter.reset).props('small outline')
 
-ui.run()
+ui.run(port=1234)

+ 12 - 1
nicegui/nicegui.py

@@ -5,6 +5,7 @@ from typing import Dict
 
 from fastapi import FastAPI
 from fastapi.middleware.gzip import GZipMiddleware
+from fastapi.responses import FileResponse
 from fastapi.staticfiles import StaticFiles
 from fastapi_socketio import SocketManager
 
@@ -12,7 +13,7 @@ from . import binding, globals, vue
 from .client import Client
 from .task_logger import create_task
 
-globals.app = app = FastAPI(routes=vue.generate_js_routes())
+globals.app = app = FastAPI()
 globals.sio = sio = SocketManager(app=app)._sio
 
 app.add_middleware(GZipMiddleware)
@@ -26,6 +27,16 @@ def index():
     return globals.client_stack[-1].build_response()
 
 
+@app.get('/_vue/dependencies/{path:path}')
+def vue_dependencies(path: str):
+    return FileResponse(path, media_type='text/javascript')
+
+
+@app.get('/_vue/components/{name}')
+def vue_dependencies(name: str):
+    return FileResponse(vue.js_components[name], media_type='text/javascript')
+
+
 @app.on_event('startup')
 def on_startup() -> None:
     globals.loop = asyncio.get_running_loop()

+ 20 - 45
nicegui/vue.py

@@ -1,33 +1,29 @@
-from dataclasses import dataclass
 from pathlib import Path
 from typing import Dict, List, Tuple
 
 import vbuild
-from starlette.responses import FileResponse
-from starlette.routing import Route
 
-
-@dataclass
-class Component:
-    name: str
-    path: Path
-    dependencies: List[str]
-
-
-components: Dict[str, Component] = {}
+vue_components: Dict[str, Path] = {}
+js_components: Dict[str, Path] = {}
+js_dependencies: List[Path] = []
 
 
 def register_component(name: str, py_filepath: str, component_filepath: str, dependencies: List[str] = []) -> None:
-    assert name not in components
-    components[name] = Component(
-        name=name,
-        path=Path(py_filepath).parent / component_filepath,
-        dependencies=[Path(py_filepath).parent / dependency for dependency in dependencies],
-    )
+    suffix = Path(component_filepath).suffix.lower()
+    assert suffix in ['.vue', '.js'], 'Only VUE and JS components are supported.'
+    if suffix == '.vue':
+        assert name not in vue_components, f'Duplicate VUE component name {name}'
+        vue_components[name] = Path(py_filepath).parent / component_filepath
+    elif suffix == '.js':
+        assert name not in js_dependencies, f'Duplicate JS component name {name}'
+        js_components[name] = Path(py_filepath).parent / component_filepath
+    for dependency in dependencies:
+        assert Path(dependency).suffix == '.js', 'Only JS dependencies are supported.'
+        js_dependencies.append(Path(py_filepath).parent / dependency)
 
 
 def generate_vue_content() -> Tuple[str]:
-    builds = [vbuild.VBuild(c.name, c.path.read_text()) for c in components.values() if c.path.suffix == '.vue']
+    builds = [vbuild.VBuild(name, path.read_text()) for name, path in vue_components.items()]
     return (
         '\n'.join(v.html for v in builds),
         '<style>' + '\n'.join(v.style for v in builds) + '</style>',
@@ -35,32 +31,11 @@ def generate_vue_content() -> Tuple[str]:
     )
 
 
-def get_js_components() -> List[Component]:
-    return [c for c in components.values() if c.path.suffix == '.js']
-
-
-def generate_js_routes() -> List[Route]:
-    routes: List[Route] = []
-    for component in components.values():
-        for dependency in component.dependencies:
-            routes.append(Route(f'/_vue/{component.name}/{dependency}',
-                                lambda _, path=dependency: FileResponse(path, media_type='text/javascript')))
-    for component in get_js_components():
-        routes.append(Route(f'/_vue/{component.name}',
-                            lambda _, path=component.path: FileResponse(path, media_type='text/javascript')))
-    return routes
-
-
 def generate_js_imports() -> str:
     result = ''
-    for component in components.values():
-        for dependency in component.dependencies:
-            result += f'''
-                import "/_vue/{component.name}/{dependency}";
-            '''
-    for component in get_js_components():
-        result += f'''
-            import {{ default as {component.name} }} from "/_vue/{component.name}";
-            app.component("{component.name}", {component.name});
-        '''
+    for path in js_dependencies:
+        result += f'import "/_vue/dependencies/{path}";\n'
+    for name, path in js_components.items():
+        result += f'import {{ default as {name} }} from "/_vue/components/{name}";\n'
+        result += f'app.component("{name}", {name});\n'
     return result