feat: support HEIF/HEIC image uploads via client-side Canvas conversion
Convert all image files to JPEG on the client before uploading, allowing iPhone HEIF/HEIC photos to work without server-side libheif dependency.
This commit is contained in:
parent
bb4aa103fb
commit
fbf5a288c1
5 changed files with 48 additions and 26 deletions
|
|
@ -56,6 +56,7 @@ pub(super) fn router() -> axum::Router<AppState> {
|
|||
)
|
||||
.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 {
|
||||
(
|
||||
[
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = "";
|
||||
});
|
||||
}
|
||||
|
|
|
|||
18
static/js/image-utils.js
Normal file
18
static/js/image-utils.js
Normal file
|
|
@ -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);
|
||||
});
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
src="https://cdn.jsdelivr.net/gh/starfederation/datastar@1.0.0-RC.6/bundles/datastar.js"
|
||||
></script>
|
||||
<script defer src="/static/js/location.js"></script>
|
||||
<script defer src="/static/js/image-utils.js"></script>
|
||||
<script defer src="/static/js/components/photo-capture.js"></script>
|
||||
<script defer src="/static/js/components/searchable-select.js"></script>
|
||||
<script defer src="/static/js/components/chip-scroll.js"></script>
|
||||
|
|
|
|||
Loading…
Reference in a new issue