From 8b9e855c57c8879934f9ccc8fa17f73966465a17 Mon Sep 17 00:00:00 2001 From: "(quasar) nebula" Date: Wed, 1 Mar 2023 22:59:41 -0400 Subject: image overlay download progress bar --- src/static/client.js | 82 ++++++++++++++++++++++++++++++-- src/static/site3.css | 28 +++++++++++ src/write/build-modes/live-dev-server.js | 6 ++- 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); -- cgit 1.3.0-6-gf8a5