« get me outta code hell

image overlay download progress bar - hsmusic-wiki - HSMusic - static wiki software cataloguing collaborative creation
about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
author(quasar) nebula <qznebula@protonmail.com>2023-03-01 22:59:41 -0400
committer(quasar) nebula <qznebula@protonmail.com>2023-03-01 22:59:41 -0400
commit8b9e855c57c8879934f9ccc8fa17f73966465a17 (patch)
tree9b72d98c500fecca903483722a9d8fe242757da2 /src
parent0f7e9e5dfb429b14b816370dc1472ee4c2ae82eb (diff)
image overlay download progress bar
Diffstat (limited to 'src')
-rw-r--r--src/static/client.js82
-rw-r--r--src/static/site3.css28
-rw-r--r--src/write/build-modes/live-dev-server.js6
3 files changed, 111 insertions, 5 deletions
diff --git a/src/static/client.js b/src/static/client.js
index addc93a4..5b8b0c8a 100644
--- a/src/static/client.js
+++ b/src/static/client.js
@@ -677,17 +677,30 @@ function handleImageLinkClicked(evt) {
   const mainThumbSize = getPreferredThumbSize();
 
   const source = evt.target.closest('a').href;
-  mainImage.src = source.replace(/\.(jpg|png)$/, `.${mainThumbSize}.jpg`);
-  thumbImage.src = source.replace(/\.(jpg|png)$/, '.small.jpg');
+
+  const mainSrc = source.replace(/\.(jpg|png)$/, `.${mainThumbSize}.jpg`);
+  const thumbSrc = source.replace(/\.(jpg|png)$/, '.small.jpg');
+
+  thumbImage.src = thumbSrc;
   for (const viewOriginal of allViewOriginal) {
     viewOriginal.href = source;
   }
 
+  mainImage.addEventListener('load', handleMainImageLoaded);
+  mainImage.addEventListener('error', handleMainImageErrored);
+
   const fileSize = evt.target.closest('a').querySelector('img').dataset.originalSize;
   updateFileSizeInformation(fileSize);
 
-  mainImage.addEventListener('load', handleMainImageLoaded);
-  mainImage.addEventListener('error', handleMainImageErrored);
+  container.style.setProperty('--download-progress', '0%');
+  loadImage(mainSrc, progress => {
+    container.style.setProperty('--download-progress', (20 + 0.8 * progress) + '%');
+  }).then(
+    blobUrl => {
+      mainImage.src = blobUrl;
+      container.style.setProperty('--download-progress', '100%');
+    },
+    handleMainImageErrored);
 
   function handleMainImageLoaded() {
     mainImage.removeEventListener('load', handleMainImageLoaded);
@@ -770,3 +783,64 @@ function updateFileSizeInformation(fileSize) {
 }
 
 addImageOverlayClickHandlers();
+
+/**
+ * Credits: Parziphal, Feb 13, 2017
+ * https://stackoverflow.com/a/42196770
+ *
+ * Loads an image with progress callback.
+ *
+ * The `onprogress` callback will be called by XMLHttpRequest's onprogress
+ * event, and will receive the loading progress ratio as an whole number.
+ * However, if it's not possible to compute the progress ratio, `onprogress`
+ * will be called only once passing -1 as progress value. This is useful to,
+ * for example, change the progress animation to an undefined animation.
+ *
+ * @param  {string}   imageUrl   The image to load
+ * @param  {Function} onprogress
+ * @return {Promise}
+ */
+function loadImage(imageUrl, onprogress) {
+  return new Promise((resolve, reject) => {
+    var xhr = new XMLHttpRequest();
+    var notifiedNotComputable = false;
+
+    xhr.open('GET', imageUrl, true);
+    xhr.responseType = 'arraybuffer';
+
+    xhr.onprogress = function(ev) {
+      if (ev.lengthComputable) {
+        onprogress(parseInt((ev.loaded / ev.total) * 1000) / 10);
+      } else {
+        if (!notifiedNotComputable) {
+          notifiedNotComputable = true;
+          onprogress(-1);
+        }
+      }
+    }
+
+    xhr.onloadend = function() {
+      if (!xhr.status.toString().match(/^2/)) {
+        reject(xhr);
+      } else {
+        if (!notifiedNotComputable) {
+          onprogress(100);
+        }
+
+        var options = {}
+        var headers = xhr.getAllResponseHeaders();
+        var m = headers.match(/^Content-Type:\s*(.*?)$/mi);
+
+        if (m && m[1]) {
+          options.type = m[1];
+        }
+
+        var blob = new Blob([this.response], options);
+
+        resolve(window.URL.createObjectURL(blob));
+      }
+    }
+
+    xhr.send();
+  });
+}
diff --git a/src/static/site3.css b/src/static/site3.css
index 484c9f9d..bdb2623b 100644
--- a/src/static/site3.css
+++ b/src/static/site3.css
@@ -1400,6 +1400,34 @@ main.long-content .content-sticky-heading-container .content-sticky-subheading-r
   transition: opacity 0.25s;
 }
 
+#image-overlay-image-container::after {
+  content: "";
+  display: block;
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  height: 4px;
+  width: var(--download-progress);
+  background: var(--primary-color);
+  box-shadow: 0 -3px 12px 4px var(--primary-color);
+  transition: 0.25s;
+}
+
+#image-overlay-container.loaded #image-overlay-image-container::after {
+  width: 100%;
+  background: white;
+  opacity: 0;
+}
+
+#image-overlay-container.errored #image-overlay-image-container::after {
+  width: 100%;
+  background: red;
+}
+
+#image-overlay-container:not(.visible) #image-overlay-image-container::after {
+  width: 0 !important;
+}
+
 #image-overlay-action-container {
   padding: 4px 4px 6px 4px;
   border-radius: 0 0 5px 5px;
diff --git a/src/write/build-modes/live-dev-server.js b/src/write/build-modes/live-dev-server.js
index dfebda0e..6dfa7d71 100644
--- a/src/write/build-modes/live-dev-server.js
+++ b/src/write/build-modes/live-dev-server.js
@@ -226,7 +226,11 @@ export async function go({
       }[extname];
 
       try {
-        response.writeHead(200, contentType ? {'Content-Type': contentType} : {});
+        const {size} = await stat(filePath);
+        response.writeHead(200, contentType ? {
+          'Content-Type': contentType,
+          'Content-Length': size,
+        } : {});
         await pipeline(
           createReadStream(filePath),
           response);