diff --git a/src/application/routes/app/mod.rs b/src/application/routes/app/mod.rs index 6cc3802..7b9f21f 100644 --- a/src/application/routes/app/mod.rs +++ b/src/application/routes/app/mod.rs @@ -56,6 +56,7 @@ pub(super) fn router() -> axum::Router { ) .route("/static/js/components/chip-scroll.js", get(chip_scroll_js)) .route("/static/js/location.js", get(location_js)) + .route("/static/js/image-utils.js", get(image_utils_js)) .route("/static/js/components/world-map.js", get(world_map_js)) .route("/static/js/components/donut-chart.js", get(donut_chart_js)) .route( @@ -102,6 +103,16 @@ async fn location_js() -> impl IntoResponse { ) } +async fn image_utils_js() -> impl IntoResponse { + ( + [ + ("content-type", "application/javascript; charset=utf-8"), + ("cache-control", "public, max-age=604800"), + ], + include_str!("../../../../static/js/image-utils.js"), + ) +} + async fn photo_capture_js() -> impl IntoResponse { ( [ diff --git a/static/js/components/image-upload.js b/static/js/components/image-upload.js index 2326d50..a76c733 100644 --- a/static/js/components/image-upload.js +++ b/static/js/components/image-upload.js @@ -37,28 +37,23 @@ customElements.define( }); } - _handleFile(file) { + async _handleFile(file) { const entityType = this.getAttribute("entity-type"); const entityId = this.getAttribute("entity-id"); const mode = this.getAttribute("mode"); - const reader = new FileReader(); - reader.onload = () => { - const dataUrl = reader.result; + const dataUrl = await imageToJpegDataUrl(file); - if (entityType && entityId && mode !== "deferred") { - this._upload(entityType, entityId, dataUrl); - } else { - // Deferred mode: store data URL in a target hidden input - const targetId = this.getAttribute("target-input"); - if (targetId) { - document.getElementById(targetId).value = dataUrl; - } - this._showPreview(dataUrl); + if (entityType && entityId && mode !== "deferred") { + this._upload(entityType, entityId, dataUrl); + } else { + // Deferred mode: store data URL in a target hidden input + const targetId = this.getAttribute("target-input"); + if (targetId) { + document.getElementById(targetId).value = dataUrl; } - }; - - reader.readAsDataURL(file); + this._showPreview(dataUrl); + } } _showPreview(dataUrl) { diff --git a/static/js/components/photo-capture.js b/static/js/components/photo-capture.js index 31c5c28..a833903 100644 --- a/static/js/components/photo-capture.js +++ b/static/js/components/photo-capture.js @@ -12,18 +12,15 @@ customElements.define( if (e.target !== input) input.click(); }); - input.addEventListener("change", () => { + input.addEventListener("change", async () => { const file = input.files[0]; if (!file) return; - const reader = new FileReader(); - reader.onload = () => { - document.getElementById(this.getAttribute("target-input")).value = - reader.result; - document - .getElementById(this.getAttribute("target-form")) - .requestSubmit(); - }; - reader.readAsDataURL(file); + const dataUrl = await imageToJpegDataUrl(file); + document.getElementById(this.getAttribute("target-input")).value = + dataUrl; + document + .getElementById(this.getAttribute("target-form")) + .requestSubmit(); input.value = ""; }); } diff --git a/static/js/image-utils.js b/static/js/image-utils.js new file mode 100644 index 0000000..f9cfed9 --- /dev/null +++ b/static/js/image-utils.js @@ -0,0 +1,18 @@ +/** Convert any image file (HEIC, AVIF, WebP, PNG, etc.) to a JPEG data URL via Canvas. */ +const imageToJpegDataUrl = (file) => + new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement("canvas"); + canvas.width = img.naturalWidth; + canvas.height = img.naturalHeight; + canvas.getContext("2d").drawImage(img, 0, 0); + URL.revokeObjectURL(img.src); + resolve(canvas.toDataURL("image/jpeg", 0.92)); + }; + img.onerror = () => { + URL.revokeObjectURL(img.src); + reject(new Error("Failed to load image")); + }; + img.src = URL.createObjectURL(file); + }); diff --git a/templates/base.html b/templates/base.html index 6719fc4..0f26c0e 100644 --- a/templates/base.html +++ b/templates/base.html @@ -49,6 +49,7 @@ src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.6/bundles/datastar.js" > +