| | |
| | | ], |
| | | }); |
| | | |
| | | window.jvbUploadBlobs = this.uploadStore; |
| | | |
| | | // Subscribe to store events |
| | | this.fieldStore.subscribe(this.handleFieldStoreEvent.bind(this)); |
| | | this.uploadStore.subscribe(this.handleUploadStoreEvent.bind(this)); |
| | |
| | | // 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(); |
| | |
| | | '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(); |
| | | } |
| | | |
| | |
| | | if (config.destination === 'post_group' && !this.dragController) { |
| | | this.initGroupFeatures(); |
| | | } |
| | | if (config.type !== 'single') { |
| | | this.initSortable(field); |
| | | } |
| | | |
| | | return fieldId; |
| | | } |
| | |
| | | }); |
| | | } |
| | | |
| | | 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) |
| | | *******************************************************************************/ |
| | |
| | | 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', |
| | |
| | | } |
| | | } |
| | | 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); |
| | |
| | | this.selectionHandlers.clear(); |
| | | |
| | | this.cleanupAllPreviewUrls(); |
| | | this.sortableInstances.forEach(instance => { |
| | | if (instance?.destroy) { |
| | | instance.destroy(); |
| | | } |
| | | }); |
| | | this.sortableInstances.clear(); |
| | | |
| | | // Clear data |
| | | this.fields.clear(); |
| | |
| | | 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(); |