From e9967fa22781d922ba4eb8fb44fe72d200ac4b14 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 10 Nov 2025 21:04:10 +0000
Subject: [PATCH] =IconsManager.php update

---
 assets/js/concise/UploadManager.js |  126 ++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 120 insertions(+), 6 deletions(-)

diff --git a/assets/js/concise/UploadManager.js b/assets/js/concise/UploadManager.js
index 7144428..b59101c 100644
--- a/assets/js/concise/UploadManager.js
+++ b/assets/js/concise/UploadManager.js
@@ -38,6 +38,8 @@
 			],
 		});
 
+		window.jvbUploadBlobs = this.uploadStore;
+
 		// Subscribe to store events
 		this.fieldStore.subscribe(this.handleFieldStoreEvent.bind(this));
 		this.uploadStore.subscribe(this.handleUploadStoreEvent.bind(this));
@@ -48,7 +50,6 @@
 		// Core data structures
 		this.fields = new Map();
 		this.uploads = new Map();
-		this.uploadBlobs = new Map();
 		this.groups = new Map();
 		this.selected = new Map();
 		this.selectionHandlers = new Map();
@@ -98,6 +99,20 @@
 			'failed_permanent': 'Upload failed permanently'
 		};
 
+		// Sortable configuration
+		this.sortableInstances = new Map();
+		this.sortableConfig = {
+			animation: 150,
+			draggable: '.item',
+			handle: '.select-item-label, img', // Can drag by image or checkbox label
+			ghostClass: 'sortable-ghost',
+			chosenClass: 'sortable-chosen',
+			dragClass: 'sortable-drag',
+			onEnd: (evt) => {
+				this.handleReorder(evt);
+			}
+		};
+
 		this.init();
 	}
 
@@ -209,6 +224,9 @@
 		if (config.destination === 'post_group' && !this.dragController) {
 			this.initGroupFeatures();
 		}
+		if (config.type !== 'single') {
+			this.initSortable(field);
+		}
 
 		return fieldId;
 	}
@@ -355,6 +373,76 @@
 		});
 	}
 
+	initSortable(field) {
+		if (!window.Sortable) return;
+
+		// Main grid
+		const mainGrid = field.element.querySelector('.item-grid:not(.group)');
+		if (mainGrid) {
+			this.sortableInstances.set(`${field.id}-main`,
+				new Sortable(mainGrid, {
+					...this.sortableConfig,
+					group: {
+						name: field.id,
+						pull: true,
+						put: true
+					}
+				})
+			);
+		}
+
+		// Group grids (for selection mode with grouping)
+		const groupGrids = field.element.querySelectorAll('.item-grid.group');
+		groupGrids.forEach((grid, index) => {
+			this.sortableInstances.set(`${field.id}-group-${index}`,
+				new Sortable(grid, {
+					...this.sortableConfig,
+					group: {
+						name: field.id,
+						pull: true,
+						put: true
+					}
+				})
+			);
+		});
+	}
+
+// Add reorder handler
+	handleReorder(evt) {
+		const grid = evt.to;
+		const fieldWrapper = grid.closest('.field, .upload');
+		if (!fieldWrapper) return;
+
+		const form = fieldWrapper.closest('form');
+		if (!form) return;
+
+		// Get form config if available
+		const formId = form.dataset.formId;
+		if (formId && window.jvbForms) {
+			const formConfig = window.jvbForms.forms?.get(formId);
+			if (formConfig?.options.autosave) {
+				// Trigger autosave after reordering
+				window.jvbForms.scheduleSave(formConfig, 1000);
+			}
+		}
+
+		// Announce for accessibility
+		if (window.jvbA11y) {
+			window.jvbA11y.announce('Item reordered');
+		}
+
+		// Trigger custom event
+		fieldWrapper.dispatchEvent(new CustomEvent('jvb-items-reordered', {
+			detail: {
+				from: evt.from,
+				to: evt.to,
+				oldIndex: evt.oldIndex,
+				newIndex: evt.newIndex
+			},
+			bubbles: true
+		}));
+	}
+
 	/*******************************************************************************
 	 * EXTERNAL FILE DROP HANDLERS (for new uploads from desktop)
 	 *******************************************************************************/
@@ -1750,6 +1838,9 @@
 		formData.append('posts', JSON.stringify(posts));
 		formData.append('upload_ids', JSON.stringify(uploadMap));
 
+		for (const [key, value] of formData.entries()) {
+			console.log(key, value);
+		}
 		const operation = {
 			endpoint: 'uploads/groups',
 			method: 'POST',
@@ -2861,11 +2952,14 @@
 		}
 	}
 	async saveUpload(upload) {
-		// Handle blob data separately
-		if (upload.file instanceof File || upload.file instanceof Blob) {
-			await this.uploadStore.saveBlob(upload.id, upload.file);
-			// Don't store the file in the main store
-			const { file, originalFile, ...cleanUpload } = upload;
+		// Use the processed file if available, otherwise original
+		const fileToStore = upload.processedFile || upload.originalFile || upload.file;
+
+		if (fileToStore instanceof File || fileToStore instanceof Blob) {
+			await this.uploadStore.saveBlob(upload.id, fileToStore);
+
+			// Don't store file objects in main store
+			const { file, originalFile, processedFile, ...cleanUpload } = upload;
 			await this.uploadStore.save(cleanUpload);
 		} else {
 			await this.uploadStore.save(upload);
@@ -2929,6 +3023,12 @@
 		this.selectionHandlers.clear();
 
 		this.cleanupAllPreviewUrls();
+		this.sortableInstances.forEach(instance => {
+			if (instance?.destroy) {
+				instance.destroy();
+			}
+		});
+		this.sortableInstances.clear();
 
 		// Clear data
 		this.fields.clear();
@@ -2938,6 +3038,20 @@
 		this.subscribers.clear();
 	}
 
+	destroySortable(fieldName) {
+		// Destroy all sortable instances for this field
+		const instances = Array.from(this.sortableInstances.keys())
+			.filter(key => key.startsWith(fieldName));
+
+		instances.forEach(key => {
+			const instance = this.sortableInstances.get(key);
+			if (instance?.destroy) {
+				instance.destroy();
+			}
+			this.sortableInstances.delete(key);
+		});
+	}
+
 	cleanupRestore() {
 		this.restoreModal.handleClose();
 		this.restoreSelection.destroy();

--
Gitblit v1.10.0