diff --git a/html/static/js/photo-upload.js b/html/static/js/photo-upload.js
index 2fa896cc..d3bb2ef3 100644
--- a/html/static/js/photo-upload.js
+++ b/html/static/js/photo-upload.js
@@ -4,7 +4,10 @@ class PhotoUpload extends HTMLElement {
constructor() {
super();
- this.attachShadow({ mode: 'open' });
+ this.attachShadow({ mode: "open" });
+ // Track all selected files
+ this.selectedFiles = new Map();
+ this.fileCounter = 0;
this.render();
this.fileInput = this.shadowRoot.getElementById("photos");
this.internals = this.attachInternals();
@@ -16,168 +19,193 @@ class PhotoUpload extends HTMLElement {
_initializeUploader() {
// Elements
- const photoInput = this.shadowRoot.querySelector('#photos');
+ const photoInput = this.shadowRoot.querySelector("#photos");
// Handle photo selection
- photoInput.addEventListener('change', () => {this._handlePhotoSelection()});
- photoInput.addEventListener("input", (event) => {
- let value = event.textContent;
- const n = "photos";
- let i = 0
- const entries = new FormData();
- for (const file of event.target.files) {
- entries.append(n + i, file);
- i = i + 1
- }
- this.internals.setFormValue(entries);
+ photoInput.addEventListener("change", () => {
+ this._handlePhotoSelection();
+ this._updateFormValue();
});
+
// Handle drag and drop
- const photoDropArea = this.shadowRoot.querySelector('#photoDropArea');
-
- photoDropArea.addEventListener('dragover', function(e) {
+ const photoDropArea = this.shadowRoot.querySelector("#photoDropArea");
+
+ photoDropArea.addEventListener("dragover", (e) => {
e.preventDefault();
- photoDropArea.style.backgroundColor = '#e9ecef';
+ photoDropArea.style.backgroundColor = "#e9ecef";
});
-
- photoDropArea.addEventListener('dragleave', function() {
- photoDropArea.style.backgroundColor = '#f8f9fa';
+
+ photoDropArea.addEventListener("dragleave", () => {
+ photoDropArea.style.backgroundColor = "#f8f9fa";
});
-
- photoDropArea.addEventListener('drop', function(e) {
+
+ photoDropArea.addEventListener("drop", (e) => {
e.preventDefault();
- photoDropArea.style.backgroundColor = '#f8f9fa';
-
+ photoDropArea.style.backgroundColor = "#f8f9fa";
+
if (e.dataTransfer.files.length) {
- handleFiles(e.dataTransfer.files);
+ this._handleFiles(e.dataTransfer.files);
}
});
}
+
+ // Update form value with all selected files
+ _updateFormValue() {
+ const entries = new FormData();
+ for (const [fileId, file] of this.selectedFiles.entries()) {
+ entries.append(`photo_${fileId}`, file);
+ }
+ this.internals.setFormValue(entries);
+ }
+
+ // Handle files from drag and drop
+ _handleFiles(files) {
+ // Set the files to the input element
+ // (Not directly possible, but we can process them manually)
+ Array.from(files).forEach((file) => {
+ if (file.type.match("image.*")) {
+ const fileId = this.fileCounter++;
+ this.selectedFiles.set(fileId, file);
+ this._createImagePreview(file, fileId);
+ }
+ });
+
+ this._updateFormValue();
+ }
+
render() {
const style = `
-
-
- `;
+ .photo-preview img {
+ width: 80px;
+ height: 80px;
+ object-fit: cover;
+ border-radius: 4px;
+ }
+
+ `;
// Create the table
let html = `
-
-
-
-
-
-
-
Take pictures of the mosquito problem area
+
+
+
+
+
+
+
Take pictures of the mosquito problem area
-
-
-
-
-
- `;
+
+
+
+
+
+ `;
// Set the shadow DOM content
this.shadowRoot.innerHTML = style + html;
this.shadowRoot.handleButtonClick = () => {
- const photoInput = this.shadowRoot.querySelector('#photos');
+ const photoInput = this.shadowRoot.querySelector("#photos");
photoInput.click();
};
}
+
+ /**
+ * Create an image preview for a single file
+ */
+ _createImagePreview(file, fileId) {
+ const photoPreviewContainer = this.shadowRoot.querySelector(
+ "#photoPreviewContainer",
+ );
+
+ // Create preview container
+ const previewContainer = document.createElement("div");
+ previewContainer.className = "position-relative m-1";
+ previewContainer.dataset.fileId = fileId;
+
+ // Create image preview
+ const img = document.createElement("img");
+ img.className = "img-thumbnail";
+ img.style.width = "100px";
+ img.style.height = "100px";
+ img.style.objectFit = "cover";
+
+ // Read file and set preview
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ img.src = e.target.result;
+ };
+ reader.readAsDataURL(file);
+
+ // Create remove button
+ const removeBtn = document.createElement("button");
+ removeBtn.type = "button";
+ removeBtn.className = "btn btn-sm btn-danger position-absolute top-0 end-0";
+ removeBtn.innerHTML = "×";
+ removeBtn.style.fontSize = "10px";
+ removeBtn.style.padding = "0 5px";
+
+ // Handle remove button click
+ removeBtn.addEventListener("click", () => {
+ // Remove this file from our collection
+ this.selectedFiles.delete(parseInt(previewContainer.dataset.fileId));
+ // Update the form value
+ this._updateFormValue();
+ // Remove the preview
+ previewContainer.remove();
+ });
+
+ // Add elements to the preview container
+ previewContainer.appendChild(img);
+ previewContainer.appendChild(removeBtn);
+ photoPreviewContainer.appendChild(previewContainer);
+ }
+
/**
* Handle photo selection and preview
*/
_handlePhotoSelection() {
- const photoInput = this.shadowRoot.querySelector('#photos');
- const photoPreviewContainer = this.shadowRoot.querySelector('#photoPreviewContainer');
-
- // Clear previous previews
- photoPreviewContainer.innerHTML = '';
+ const photoInput = this.shadowRoot.querySelector("#photos");
// Check if files were selected
if (photoInput.files && photoInput.files.length > 0) {
// Loop through selected files
- Array.from(photoInput.files).forEach((file, index) => {
- console.log("Handling", index, file);
- if (!file.type.match('image.*')) {
+ Array.from(photoInput.files).forEach((file) => {
+ if (!file.type.match("image.*")) {
console.log("Skipping non-image file", file.type);
return; // Skip non-image files
}
-
- // Create preview container
- const previewContainer = document.createElement('div');
- previewContainer.className = 'position-relative m-1';
-
- // Create image preview
- const img = document.createElement('img');
- img.className = 'img-thumbnail';
- img.style.width = '100px';
- img.style.height = '100px';
- img.style.objectFit = 'cover';
-
- // Read file and set preview
- const reader = new FileReader();
- reader.onload = (e) => {
- img.src = e.target.result;
- };
- reader.readAsDataURL(file);
-
- // Create remove button
- const removeBtn = document.createElement('button');
- removeBtn.type = 'button';
- removeBtn.className = 'btn btn-sm btn-danger position-absolute top-0 end-0';
- removeBtn.innerHTML = '×';
- removeBtn.style.fontSize = '10px';
- removeBtn.style.padding = '0 5px';
-
- // Handle remove button click
- removeBtn.addEventListener('click', function() {
- // Create a new FileList without this file
- // Since FileList is immutable, we need to reset the input
- // This is a bit tricky and requires recreating the input
- previewContainer.remove();
-
- // If this was the last image, clear the input entirely
- if (photoPreviewContainer.children.length === 0) {
- photoInput.value = '';
- }
- // Note: Unfortunately, selectively removing files from a FileList isn't straightforward
- // In a real implementation, we might track selected files in an array and recreate the input
- });
-
- // Add elements to the preview container
- previewContainer.appendChild(img);
- previewContainer.appendChild(removeBtn);
- photoPreviewContainer.appendChild(previewContainer);
+
+ // Add file to our collection with unique ID
+ const fileId = this.fileCounter++;
+ this.selectedFiles.set(fileId, file);
+
+ // Create and add preview
+ this._createImagePreview(file, fileId);
});
}
}
-
}
// Register the custom element
-customElements.define('photo-upload', PhotoUpload);
+customElements.define("photo-upload", PhotoUpload);