aboutsummaryrefslogtreecommitdiff
path: root/static/scripts/upload.js
diff options
context:
space:
mode:
authorBobby <[email protected]>2025-07-17 10:47:08 +0530
committerBobby <[email protected]>2025-07-17 10:47:08 +0530
commitb0ba363696a758a8d0637107bd29a0a9ac1382d4 (patch)
treef11acd0ebc5a4b3d633a6a596deee92b575f8f1c /static/scripts/upload.js
parent94cca506f6d1461bf38afa5b0e38d778391b8d39 (diff)
downloadimageboard-b0ba363696a758a8d0637107bd29a0a9ac1382d4.tar.xz
imageboard-b0ba363696a758a8d0637107bd29a0a9ac1382d4.zip
refactor and fake upload
Diffstat (limited to 'static/scripts/upload.js')
-rw-r--r--static/scripts/upload.js236
1 files changed, 231 insertions, 5 deletions
diff --git a/static/scripts/upload.js b/static/scripts/upload.js
index 309b0da..d190014 100644
--- a/static/scripts/upload.js
+++ b/static/scripts/upload.js
@@ -6,6 +6,12 @@
const imageBlobMapping = new Map();
/**
+ * Tracks whether an upload is currently in progress
+ * @type {boolean}
+ */
+let isUploading = false;
+
+/**
* Shows or hides the Upload All button in the .upload-area container based on imageBlobMapping size.
* The button is created once and appended/removed as needed.
* @function
@@ -19,11 +25,8 @@ function updateUploadAllBtn() {
uploadAllBtn.type = 'button';
uploadAllBtn.textContent = 'Upload All';
uploadAllBtn.className = 'upload-all-btn';
- /**
- * TODO: Implement actual upload logic here
- */
- uploadAllBtn.onclick = function () {
- alert('Upload All clicked!');
+ uploadAllBtn.onclick = async function () {
+ await uploadAllImages();
};
}
if (imageBlobMapping.size > 0) {
@@ -95,6 +98,7 @@ function createPreviewElement(key, blob, type, nameOrUrl) {
removeBtn.textContent = 'Remove';
removeBtn.className = 'preview-remove-btn';
removeBtn.onclick = () => {
+ if (isUploading) return; // Prevent removal during upload
previewElement.remove();
imageBlobMapping.delete(key);
updateUploadAllBtn();
@@ -369,4 +373,226 @@ function handleFiles(files) {
updateUploadAllBtn();
}
+/**
+ * Uploads all images in the imageBlobMapping to the server.
+ * Shows progress and handles success/error states.
+ */
+async function uploadAllImages() {
+ if (imageBlobMapping.size === 0) return;
+
+ const totalImages = imageBlobMapping.size;
+ let uploadedCount = 0;
+ let hasErrors = false;
+
+ // Set upload state and disable remove buttons
+ isUploading = true;
+ disableAllRemoveButtons();
+
+ // Disable the upload button and show progress
+ uploadAllBtn.disabled = true;
+ updateUploadButtonText(uploadedCount, totalImages, false);
+
+ try {
+ for (const [key, imageData] of imageBlobMapping) {
+ try {
+ // Get the selected rating for this image
+ const selectedRating = getSelectedRating(key);
+ imageData.rating = selectedRating;
+
+ // Create and submit form for this image
+ await uploadSingleImage(imageData, selectedRating);
+
+ // Mark this image as successfully uploaded
+ markImageAsUploaded(imageData.previewElement);
+ uploadedCount++;
+ updateUploadButtonText(uploadedCount, totalImages, false);
+
+ } catch (error) {
+ console.error(`Failed to upload image ${key}:`, error);
+ markImageAsError(imageData.previewElement, error.message);
+ hasErrors = true;
+ }
+ }
+
+ // Update button text based on results
+ if (hasErrors) {
+ updateUploadButtonText(uploadedCount, totalImages, true);
+ } else {
+ uploadAllBtn.textContent = `✓ All ${totalImages} images uploaded successfully!`;
+ uploadAllBtn.className = 'upload-all-btn success';
+
+ // Clear all uploaded images after a delay
+ setTimeout(() => {
+ clearAllUploadedImages();
+ }, 2000);
+ }
+
+ } catch (error) {
+ console.error('Upload process failed:', error);
+ uploadAllBtn.textContent = 'Upload failed - Try again';
+ uploadAllBtn.className = 'upload-all-btn error';
+ uploadAllBtn.disabled = false;
+ } finally {
+ // Reset upload state
+ isUploading = false;
+ enableAllRemoveButtons();
+ }
+}
+
+/**
+ * Disables all remove buttons to prevent removal during upload.
+ */
+function disableAllRemoveButtons() {
+ const removeButtons = document.querySelectorAll('.preview-remove-btn:not(.uploaded):not(.error)');
+ removeButtons.forEach(btn => {
+ btn.disabled = true;
+ btn.classList.add('uploading');
+ });
+}
+
+/**
+ * Re-enables all remove buttons after upload is complete.
+ */
+function enableAllRemoveButtons() {
+ const removeButtons = document.querySelectorAll('.preview-remove-btn:not(.uploaded):not(.error)');
+ removeButtons.forEach(btn => {
+ btn.disabled = false;
+ btn.classList.remove('uploading');
+ });
+}
+
+/**
+ * Gets the selected rating for an image from its radio buttons.
+ * @param {string} key - The key for the image
+ * @returns {string} The selected rating
+ */
+function getSelectedRating(key) {
+ const checkedInput = document.querySelector(`input[name="rating-${key}"]:checked`);
+ return checkedInput ? checkedInput.value : 'safe';
+}
+
+/**
+ * Uploads a single image to the server by creating a FormData and submitting it.
+ * @param {Object} imageData - The image data object
+ * @param {string} rating - The selected rating
+ * @returns {Promise<void>}
+ */
+async function uploadSingleImage(imageData, rating) {
+ const formData = new FormData();
+
+ // Add the image blob to the form
+ if (imageData.type === 'local') {
+ formData.append('image', imageData.blob, imageData.nameOrUrl);
+ } else {
+ // For link images, create a File object from the blob
+ const file = new File([imageData.blob], 'image.jpg', { type: imageData.blob.type });
+ formData.append('image', file);
+ formData.append('source_url', imageData.nameOrUrl);
+ }
+
+ // Add the rating
+ formData.append('rating', rating);
+
+ // Submit to the backend
+ const response = await fetch('/posts/new', {
+ method: 'POST',
+ body: formData
+ });
+
+ if (!response.ok) {
+ const errorText = await response.text().catch(() => 'Unknown error');
+ throw new Error(errorText || `Upload failed with status ${response.status}`);
+ }
+
+ return response;
+}
+
+/**
+ * Updates the upload button text to show progress.
+ * @param {number} current - Current number of uploaded images
+ * @param {number} total - Total number of images to upload
+ * @param {boolean} hasErrors - Whether there were errors during upload
+ */
+function updateUploadButtonText(current, total, hasErrors) {
+ if (hasErrors) {
+ uploadAllBtn.textContent = `⚠ Uploaded ${current}/${total} (some failed)`;
+ uploadAllBtn.className = 'upload-all-btn warning';
+ uploadAllBtn.disabled = false;
+ } else if (current === total) {
+ uploadAllBtn.textContent = `✓ All ${total} images uploaded!`;
+ uploadAllBtn.className = 'upload-all-btn success';
+ } else {
+ uploadAllBtn.textContent = `⏳ Uploading (${current}/${total})`;
+ uploadAllBtn.className = 'upload-all-btn uploading';
+ }
+}
+
+/**
+ * Marks an image preview as successfully uploaded.
+ * @param {HTMLElement} previewElement - The preview element to mark
+ */
+function markImageAsUploaded(previewElement) {
+ previewElement.classList.add('uploaded');
+ const removeBtn = previewElement.querySelector('.preview-remove-btn');
+ if (removeBtn) {
+ removeBtn.textContent = '✓ Uploaded';
+ removeBtn.disabled = true;
+ removeBtn.className = 'preview-remove-btn uploaded';
+ }
+}
+
+/**
+ * Marks an image preview as failed to upload.
+ * @param {HTMLElement} previewElement - The preview element to mark
+ * @param {string} errorMessage - The error message to display
+ */
+function markImageAsError(previewElement, errorMessage) {
+ previewElement.classList.add('upload-error');
+ const removeBtn = previewElement.querySelector('.preview-remove-btn');
+ if (removeBtn) {
+ removeBtn.textContent = '✗ Failed';
+ removeBtn.className = 'preview-remove-btn error';
+ removeBtn.title = errorMessage;
+ }
+}
+
+/**
+ * Clears all successfully uploaded images from the preview area.
+ */
+function clearAllUploadedImages() {
+ const uploadedElements = document.querySelectorAll('.preview-area.uploaded');
+ uploadedElements.forEach(element => {
+ const key = getImageKeyFromElement(element);
+ if (key) {
+ imageBlobMapping.delete(key);
+ }
+ element.remove();
+ });
+ updateUploadAllBtn();
+
+ // Reset button state if all images are cleared
+ if (imageBlobMapping.size === 0) {
+ if (uploadAllBtn) {
+ uploadAllBtn.textContent = 'Upload All';
+ uploadAllBtn.className = 'upload-all-btn';
+ uploadAllBtn.disabled = false;
+ }
+ }
+}
+
+/**
+ * Gets the image key from a preview element by looking at its radio button names.
+ * @param {HTMLElement} previewElement - The preview element
+ * @returns {string|null} The image key or null if not found
+ */
+function getImageKeyFromElement(previewElement) {
+ const radioInput = previewElement.querySelector('input[type="radio"]');
+ if (radioInput && radioInput.name) {
+ // Extract key from "rating-{key}" format
+ const match = radioInput.name.match(/^rating-(.+)$/);
+ return match ? match[1] : null;
+ }
+ return null;
+}
+
setupLocalImageUpload();