diff libervia/web/pages/_browser/file_uploader.py @ 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
children
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),
+            )