changeset 1532:106945841fbc

_browser (album): moved code to upload file to a separate `file_uploader` module: this way, uploading logic can be re-used elsewhere.
author Goffi <goffi@goffi.org>
date Thu, 22 Jun 2023 16:36:01 +0200
parents d7c78722e4f8
children 285c49d7aad3
files libervia/web/pages/_browser/file_uploader.py libervia/web/pages/photos/album/_browser/__init__.py
diffstat 2 files changed, 123 insertions(+), 87 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libervia/web/pages/_browser/file_uploader.py	Thu Jun 22 16:36:01 2023 +0200
@@ -0,0 +1,110 @@
+import json
+from bridge import Bridge
+from browser import console as log, window
+import dialog
+from template import Template
+bridge = Bridge()
+
+log.warning = log.warn
+
+
+class FileUploader:
+    """Class handling HTTP upload and progress display"""
+
+    def __init__(self, files_service, template_file, files_path=None, on_delete_cb=None):
+        self.files_service = files_service
+        self.files_path = files_path
+        self.item_tpl = Template(template_file)
+        self.on_delete_cb = on_delete_cb
+        self.uploaded = {}
+
+    def on_progress(self, ev, item_elt):
+        if ev.lengthComputable:
+            percent = int(ev.loaded/ev.total*100)
+            self.update_progress(item_elt, percent)
+
+    def on_load(self, file_, item_elt):
+        self.update_progress(item_elt, 100)
+        item_elt.classList.add("progress_finished")
+        item_elt.classList.remove("progress_started")
+        if self.on_delete_cb is not None:
+            delete_btn = item_elt.select_one('.action_delete')
+            if delete_btn is not None:
+                delete_btn.bind("click", self.on_delete_cb)
+        log.info(f"file {file_.name} uploaded correctly")
+
+    def on_error(self, failure, file_, item_elt):
+        dialog.notification.show(
+            f"can't upload {file_.name}: {failure}",
+            level="error"
+        )
+
+    def update_progress(self, item_elt, new_value):
+        progress_elt = item_elt.select_one("progress")
+        if progress_elt is not None:
+            progress_elt.value = new_value
+            progress_elt.text = f"{new_value}%"
+
+    def on_slot_cb(self, file_, upload_slot, item_elt):
+        put_url, get_url, headers = upload_slot
+        xhr = window.XMLHttpRequest.new()
+        xhr.open("PUT", put_url, True)
+        xhr.upload.bind('progress', lambda ev: self.on_progress(ev, item_elt))
+        xhr.upload.bind('load', lambda ev: self.on_load(file_, item_elt))
+        xhr.upload.bind('error', lambda ev: self.on_error(xhr.response, file_, item_elt))
+        if self.files_path is not None:
+            xhr.setRequestHeader(
+                'Xmpp-File-Path',
+                window.encodeURIComponent(self.files_path)
+            )
+            xhr.setRequestHeader('Xmpp-File-No-Http', "true")
+        xhr.send(file_)
+
+        # we update file data with the get URL
+        file_data = json.loads(item_elt.getAttribute("data-file"))
+        file_data["url"] = get_url
+        item_elt.setAttribute("data-file", json.dumps(file_data))
+
+    def on_slot_eb(self, file_, failure, item_elt):
+        dialog.notification.show(
+            f"Can't get upload slot: {failure['message']}",
+            level="error"
+        )
+        item_elt.remove()
+
+    def upload_files(self, files, container_elt):
+        """Start file upload, and add item_tpl to container_elt"""
+        log.info(f"uploading {len(files)} files")
+        for file_ in files:
+            url = window.URL.createObjectURL(file_)
+
+            file_data = {
+                "name": file_.name,
+                "mime_type": file_.type,
+            }
+            template_file_data = file_data.copy()
+            # we don't want to open the file on click, it's not yet the
+            # uploaded URL
+            template_file_data["url"] = url
+            # we have no thumb yet, so we use the whole image
+            # TODO: reduce image for preview
+            template_file_data["thumb_url"] = url
+
+            item_elt = self.item_tpl.get_elt({
+                "file": template_file_data,
+                "uploading": True,
+            })
+            item_elt.setAttribute("data-file", json.dumps(file_data))
+            item_elt.classList.add("progress_started")
+            container_elt <= item_elt
+
+            bridge.file_http_upload_get_slot(
+                file_.name,
+                file_.size,
+                file_.type or '',
+                self.files_service,
+                callback=lambda upload_slot, file_=file_, item_elt=item_elt:
+                    self.on_slot_cb(file_, upload_slot, item_elt),
+                errback=lambda failure, file_=file_, item_elt=item_elt:
+                    self.on_slot_eb(file_, failure, item_elt),
+            )
--- a/libervia/web/pages/photos/album/_browser/__init__.py	Thu Jun 22 16:35:34 2023 +0200
+++ b/libervia/web/pages/photos/album/_browser/__init__.py	Thu Jun 22 16:36:01 2023 +0200
@@ -1,14 +1,14 @@
-from browser import document, window, bind, html, DOMNode, aio
+import alt_media_player
+from bridge import AsyncBridge, Bridge
+from browser import DOMNode, aio, bind, document, html, window
+import dialog
+from file_uploader import FileUploader
+from invitation import InvitationManager
 from javascript import JSON
-from bridge import Bridge, AsyncBridge
-from template import Template
-import dialog
+import loading
 from slideshow import SlideShow
-from invitation import InvitationManager
-import alt_media_player
-# we use tmp_aio because `blob` is not handled in Brython's aio
+from template import Template
 import tmp_aio
-import loading
 
 
 cache_path = window.cache_path
@@ -28,83 +28,7 @@
 
 # file upload
 
-def on_progress(ev, photo_elt):
-    if ev.lengthComputable:
-        percent = int(ev.loaded/ev.total*100)
-        update_progress(photo_elt, percent)
-
-
-def on_load(file_, photo_elt):
-    update_progress(photo_elt, 100)
-    photo_elt.classList.add("progress_finished")
-    photo_elt.classList.remove("progress_started")
-    photo_elt.select_one('.action_delete').bind("click", on_delete)
-    print(f"file {file_.name} uploaded correctly")
-
-
-def on_error(failure, file_, photo_elt):
-    dialog.notification.show(
-        f"can't upload {file_.name}: {failure}",
-        level="error"
-    )
-
-
-def update_progress(photo_elt, new_value):
-    progress_elt = photo_elt.select_one("progress")
-    progress_elt.value = new_value
-    progress_elt.text = f"{new_value}%"
-
-
-def on_slot_cb(file_, upload_slot, photo_elt):
-    put_url, get_url, headers = upload_slot
-    xhr = window.XMLHttpRequest.new()
-    xhr.open("PUT", put_url, True)
-    xhr.upload.bind('progress', lambda ev: on_progress(ev, photo_elt))
-    xhr.upload.bind('load', lambda ev: on_load(file_, photo_elt))
-    xhr.upload.bind('error', lambda ev: on_error(xhr.response, file_, photo_elt))
-    xhr.setRequestHeader('Xmpp-File-Path', window.encodeURIComponent(files_path))
-    xhr.setRequestHeader('Xmpp-File-No-Http', "true")
-    xhr.send(file_)
-
-
-def on_slot_eb(file_, failure, photo_elt):
-    dialog.notification.show(
-        f"Can't get upload slot: {failure['message']}",
-        level="error"
-    )
-    photo_elt.remove()
-
-
-def upload_files(files):
-    print(f"uploading {len(files)} files")
-    album_items = document['album_items']
-    for file_ in files:
-        url = window.URL.createObjectURL(file_)
-        photo_elt = photo_tpl.get_elt({
-            "file": {
-                "name": file_.name,
-                # we don't want to open the file on click, it's not yet the
-                # uploaded URL
-                "url": url,
-                # we have no thumb yet, so we use the whole image
-                # TODO: reduce image for preview
-                "thumb_url": url,
-            },
-            "uploading": True,
-        })
-        photo_elt.classList.add("progress_started")
-        album_items <= photo_elt
-
-        bridge.file_http_upload_get_slot(
-            file_.name,
-            file_.size,
-            file_.type or '',
-            files_service,
-            callback=lambda upload_slot, file_=file_, photo_elt=photo_elt:
-                on_slot_cb(file_, upload_slot, photo_elt),
-            errback=lambda failure, file_=file_, photo_elt=photo_elt:
-                on_slot_eb(file_, failure, photo_elt),
-        )
+file_uploader = FileUploader(files_service, "photo/item.html", files_path)
 
 
 @bind("#file_drop", "drop")
@@ -112,7 +36,7 @@
     evt.stopPropagation()
     evt.preventDefault()
     files = evt.dataTransfer.files
-    upload_files(files)
+    file_uploader.upload_files(files, document["album_items"])
 
 
 @bind("#file_drop", "dragover")
@@ -125,7 +49,7 @@
 @bind("#file_input", "change")
 def on_file_input_change(evt):
     files = evt.currentTarget.files
-    upload_files(files)
+    file_uploader.upload_files(files, document["album_items"])
 
 # delete
 
@@ -173,6 +97,8 @@
         cancel_cb=lambda evt, notif_elt: delete_cancel(evt, notif_elt, item_elt, item),
     )
 
+file_uploader.on_delete_cb = on_delete
+
 # cover
 
 async def cover_ok(evt, notif_elt, item_elt, item):