Explorar el Código

#656 introduce ui.download

Falko Schindler hace 2 años
padre
commit
d18347bb6b

+ 14 - 4
nicegui/client.py

@@ -52,10 +52,12 @@ class Client:
 
     @property
     def ip(self) -> Optional[str]:
+        """Return the IP address of the client, or None if the client is not connected."""
         return self.environ.get('REMOTE_ADDR') if self.environ else None
 
     @property
     def has_socket_connection(self) -> bool:
+        """Return True if the client is connected, False otherwise."""
         return self.environ is not None
 
     def __enter__(self):
@@ -88,7 +90,7 @@ class Client:
         }, status_code, {'Cache-Control': 'no-store', 'X-NiceGUI-Content': 'page'})
 
     async def connected(self, timeout: float = 3.0, check_interval: float = 0.1) -> None:
-        '''Blocks execution until the client is connected.'''
+        """Block execution until the client is connected."""
         self.is_waiting_for_connection = True
         deadline = time.time() + timeout
         while not self.environ:
@@ -98,7 +100,7 @@ class Client:
         self.is_waiting_for_connection = False
 
     async def disconnected(self, check_interval: float = 0.1) -> None:
-        '''Blocks execution until the client disconnects.'''
+        """Block execution until the client disconnects."""
         self.is_waiting_for_disconnect = True
         while self.id in globals.clients:
             await asyncio.sleep(check_interval)
@@ -106,11 +108,12 @@ class Client:
 
     async def run_javascript(self, code: str, *,
                              respond: bool = True, timeout: float = 1.0, check_interval: float = 0.01) -> Optional[str]:
-        '''Allows execution of javascript on the client.
+        """Execute JavaScript on the client.
 
         The client connection must be established before this method is called.
         You can do this by `await client.connected()` or register a callback with `client.on_connected(...)`.
-        If respond is True, the javascript code must return a string.'''
+        If respond is True, the javascript code must return a string.
+        """
         request_id = str(uuid.uuid4())
         command = {
             'code': code,
@@ -127,11 +130,18 @@ class Client:
         return self.waiting_javascript_commands.pop(request_id)
 
     def open(self, target: Union[Callable, str]) -> None:
+        """Open a new page in the client."""
         path = target if isinstance(target, str) else globals.page_routes[target]
         outbox.enqueue_message('open', path, self.id)
 
+    def download(self, url: str, filename: Optional[str] = None) -> None:
+        """Download a file from the given URL."""
+        outbox.enqueue_message('download', {'url': url, 'filename': filename}, self.id)
+
     def on_connect(self, handler: Union[Callable, Awaitable]) -> None:
+        """Register a callback to be called when the client connects."""
         self.connect_handlers.append(handler)
 
     def on_disconnect(self, handler: Union[Callable, Awaitable]) -> None:
+        """Register a callback to be called when the client disconnects."""
         self.disconnect_handlers.append(handler)

+ 14 - 0
nicegui/functions/download.py

@@ -0,0 +1,14 @@
+from typing import Optional
+
+from .. import globals
+
+
+def download(url: str, filename: Optional[str] = None) -> None:
+    """Download
+
+    Function to trigger the download of a file.
+
+    :param url: target URL of the file to download
+    :param filename: name of the file to download (default: name of the file on the server)
+    """
+    globals.get_client().download(url, filename)

+ 11 - 0
nicegui/templates/index.html

@@ -130,6 +130,16 @@
         });
       }
 
+      function download(url, filename) {
+        const anchor = document.createElement("a");
+        anchor.href = url;
+        anchor.target = "_blank";
+        anchor.download = filename || "";
+        document.body.appendChild(anchor);
+        anchor.click();
+        document.body.removeChild(anchor);
+      }
+
       const app = Vue.createApp({
         data() {
           return {
@@ -162,6 +172,7 @@
           window.socket.on("run_method", (msg) => getElement(msg.id)?.[msg.name](...msg.args));
           window.socket.on("run_javascript", (msg) => runJavascript(msg['code'], msg['request_id']));
           window.socket.on("open", (msg) => (location.href = msg));
+          window.socket.on("download", (msg) => download(msg.url, msg.filename));
           window.socket.on("notify", (msg) => Quasar.Notify.create(msg));
         },
       }).use(Quasar, {

+ 1 - 0
nicegui/ui.py

@@ -61,6 +61,7 @@ from .elements.tooltip import Tooltip as tooltip
 from .elements.tree import Tree as tree
 from .elements.upload import Upload as upload
 from .elements.video import Video as video
+from .functions.download import download
 from .functions.html import add_body_html, add_head_html
 from .functions.javascript import run_javascript
 from .functions.notify import notify

+ 23 - 0
tests/test_download.py

@@ -0,0 +1,23 @@
+from fastapi import HTTPException
+
+from nicegui import app, ui
+
+from .screen import PORT, Screen
+
+
+def test_download(screen: Screen):
+    success = False
+
+    @app.get('/static/test.py')
+    def test():
+        nonlocal success
+        success = True
+        raise HTTPException(404, 'Not found')
+
+    ui.button('Download', on_click=lambda: ui.download('static/test.py'))
+
+    screen.open('/')
+    screen.click('Download')
+    screen.wait(0.5)
+    assert success
+    screen.assert_py_logger('WARNING', f'http://localhost:{PORT}/static/test.py not found')

+ 1 - 0
website/documentation.py

@@ -410,6 +410,7 @@ def create_full() -> None:
         ui.link('show page with fancy layout', page_layout)
 
     load_demo(ui.open)
+    load_demo(ui.download)
 
     @text_demo('Sessions', '''
         The optional `request` argument provides insights about the client's URL parameters etc.

+ 5 - 0
website/more_documentation/download_documentation.py

@@ -0,0 +1,5 @@
+from nicegui import ui
+
+
+def main_demo() -> None:
+    ui.button('NiceGUI Logo', on_click=lambda: ui.download('https://nicegui.io/logo.png'))