| | |
| | | } |
| | | |
| | | async init() { |
| | | this.initializeFields(); |
| | | // this.initializeFields(); |
| | | this.initListeners(); |
| | | |
| | | // Queue integration - handle completion/failure |
| | |
| | | /******************************************************************************* |
| | | * FIELD MANAGEMENT |
| | | *******************************************************************************/ |
| | | initializeFields() { |
| | | const fields = document.querySelectorAll(this.selectors.field.field); |
| | | fields.forEach(uploader => this.registerUploader(uploader)); |
| | | } |
| | | |
| | | scanFields(container) { |
| | | scanFields(container, autoUpload) { |
| | | console.log(autoUpload, 'autoUpload'); |
| | | const fields = container.querySelectorAll(this.selectors.field.field); |
| | | fields.forEach(uploader => this.registerUploader(uploader)); |
| | | fields.forEach(uploader => this.registerUploader(uploader, autoUpload)); |
| | | } |
| | | |
| | | registerUploader(uploader) { |
| | | registerUploader(uploader, autoUpload) { |
| | | const fieldId = this.determineFieldId(uploader); |
| | | const config = this.extractFieldConfig(uploader); |
| | | const config = this.extractFieldConfig(uploader, autoUpload); |
| | | const ui = this.buildFieldUI(uploader); |
| | | |
| | | console.log(config, 'registering with config'); |
| | | // Store field data with Sets for runtime |
| | | const fieldData = { |
| | | id: fieldId, |
| | |
| | | return fieldId; |
| | | } |
| | | |
| | | extractFieldConfig(fieldElement) { |
| | | extractFieldConfig(fieldElement, autoUpload) { |
| | | return { |
| | | autoUpload: autoUpload, |
| | | destination: fieldElement.dataset.destination || 'meta', |
| | | content: fieldElement.dataset.content || null, |
| | | mode: fieldElement.dataset.mode || 'direct', |
| | |
| | | const fieldWrapper = grid.closest('.field, .upload'); |
| | | if (!fieldWrapper) return; |
| | | |
| | | const movedItems = evt.items && evt.items.length > 0 ? evt.items : [evt.item]; |
| | | |
| | | // Get current order from DOM |
| | | let items = Array.from(grid.querySelectorAll('.item:not(.sortable-ghost):not(.sortable-clone)')) |
| | | .map(upload => upload.dataset.uploadId) |
| | | .filter(id => id); |
| | | |
| | | console.log('Reordered items:', items); |
| | | |
| | | // Update hidden input (for form submission) |
| | | let hiddenInput = fieldWrapper.querySelector('input[type="hidden"]'); |
| | |
| | | hiddenInput.value = items.join(','); |
| | | } |
| | | |
| | | // ✅ Update fieldState with new order |
| | | // Update fieldState with new order |
| | | const fieldId = this.getFieldIdFromElement(grid); |
| | | if (fieldId) { |
| | | const fieldData = this.getFieldData(fieldId); |
| | |
| | | // If reordering in preview, the order is implicit by DOM position |
| | | // (we don't store preview order separately) |
| | | |
| | | this.schedulePersistance(fieldId); // ✅ Persist changes |
| | | this.schedulePersistance(fieldId); |
| | | } |
| | | |
| | | this.a11y.announce('Item reordered'); |
| | |
| | | // Meta field changes |
| | | if (fieldId) { |
| | | const fieldData = this.getFieldData(fieldId); |
| | | if (!fieldData.config.autoUpload) { |
| | | return; |
| | | } |
| | | if (fieldData?.config.destination === 'post_group') { |
| | | this.handleGroupMetaChange(e.target); |
| | | } else { |
| | |
| | | this.refreshSortable(fieldId); |
| | | |
| | | // Queue for upload if in direct mode |
| | | if (fieldData.config.destination !== 'post_group') { |
| | | if (fieldData.config.autoUpload && fieldData.config.destination !== 'post_group') { |
| | | await this.queueUpload(fieldId); |
| | | this.maybeLockUploads(fieldId); |
| | | } |
| | |
| | | popup: `Creating ${posts.length} post${posts.length > 1 ? 's' : ''}...`, |
| | | canMerge: false, |
| | | headers: { |
| | | 'action_nonce': jvbSettings.dash |
| | | 'action_nonce': window.auth.getNonce('dash') |
| | | }, |
| | | append: '_upload', |
| | | }; |
| | |
| | | title: `Uploading ${uploads.length} file${uploads.length > 1 ? 's' : ''} to server...`, |
| | | popup: `Uploading ${uploads.length} file${uploads.length > 1 ? 's' : ''}...`, |
| | | canMerge: false, |
| | | headers: { 'action_nonce': jvbSettings.dash }, |
| | | headers: { 'action_nonce': window.auth.getNonce('dash') }, |
| | | append: '_upload' |
| | | }; |
| | | |
| | |
| | | data: queueData, |
| | | title: 'Updating meta', |
| | | canMerge: true, |
| | | headers: { 'action_nonce': jvbSettings.dash } |
| | | headers: { 'action_nonce': window.auth.getNonce('dash') } |
| | | }; |
| | | |
| | | try { |
| | |
| | | this.refreshSortable(fieldKey); |
| | | |
| | | // Queue for upload if needed |
| | | if (config.mode === 'direct' && config.destination !== 'post_group') { |
| | | console.log(config); |
| | | if (config.autoUpload && config.mode === 'direct' && config.destination !== 'post_group') { |
| | | await this.queueUpload(fieldKey); |
| | | } |
| | | } |
| | |
| | | storedGroup.changes = { ...groupData.changes }; |
| | | } |
| | | |
| | | // ✅ Preserve upload order |
| | | // Preserve upload order |
| | | if (groupData.uploads) { |
| | | storedGroup.uploads = [...groupData.uploads]; |
| | | } |
| | |
| | | // Can add custom logic here if needed |
| | | } |
| | | |
| | | getCurrentSelection(fieldId) { |
| | | let selected = []; |
| | | for (let [key, handler] of this.selectionHandlers) { |
| | | if ((fieldId === key || key.includes(fieldId)) && handler.selectedItems.size > 0) { |
| | | selected = selected.concat([...handler.selectedItems]); |
| | | } |
| | | } |
| | | return selected; |
| | | } |
| | | |
| | | /******************************************************************************* |
| | | * HELPER METHODS |
| | | *******************************************************************************/ |
| | |
| | | * Save field data to store, converting Sets to Arrays |
| | | */ |
| | | async saveFieldData(fieldData) { |
| | | console.log('💾 Saving:', fieldData.id, { |
| | | uploads: fieldData.uploads?.size, |
| | | groups: fieldData.groups?.length |
| | | }); |
| | | |
| | | await this.fieldStore.save({ |
| | | ...fieldData, |
| | | timestamp: Date.now() |
| | |
| | | return progress[status] || 0; |
| | | } |
| | | |
| | | getModalType(fieldEl) { |
| | | if (!fieldEl?.element) return null; |
| | | if (fieldEl._cachedModalType !== undefined) { |
| | | return fieldEl._cachedModalType; |
| | | } |
| | | |
| | | const dialog = fieldEl.element.closest('dialog'); |
| | | if (!dialog) { |
| | | fieldEl._cachedModalType = null; |
| | | return null; |
| | | } |
| | | |
| | | let modalType = null; |
| | | if (dialog.classList.contains('edit')) modalType = 'edit'; |
| | | else if (dialog.classList.contains('create')) modalType = 'create'; |
| | | else if (dialog.classList.contains('bulkEdit')) modalType = 'bulkEdit'; |
| | | else modalType = dialog.className; |
| | | |
| | | fieldEl._cachedModalType = modalType; |
| | | return modalType; |
| | | } |
| | | |
| | | createUploadElement(upload, draggable = false) { |
| | | let image = window.getTemplate('uploadItem'); |
| | |
| | | * PERSISTENCE |
| | | *******************************************************************************/ |
| | | |
| | | /** |
| | | * Normalize field data loaded from IndexedDB |
| | | * Converts Arrays back to Sets, handles missing properties |
| | | */ |
| | | normalizeFieldData(fieldData) { |
| | | if (!fieldData) return null; |
| | | |
| | | // Convert uploads array back to Set |
| | | if (Array.isArray(fieldData.uploads)) { |
| | | fieldData.uploads = new Set(fieldData.uploads); |
| | | } else if (!fieldData.uploads) { |
| | | fieldData.uploads = new Set(); |
| | | } |
| | | |
| | | // Convert groups array, ensure proper structure |
| | | if (!Array.isArray(fieldData.groups)) { |
| | | fieldData.groups = []; |
| | | } |
| | | |
| | | // Ensure each group has uploads array |
| | | fieldData.groups = fieldData.groups.map(group => ({ |
| | | ...group, |
| | | uploads: Array.isArray(group.uploads) ? group.uploads : [] |
| | | })); |
| | | |
| | | return fieldData; |
| | | } |
| | | |
| | | schedulePersistance(fieldId) { |
| | | const key = `persist_${fieldId}`; |
| | | window.debouncer.schedule( |
| | |
| | | } |
| | | |
| | | /******************************************************************************* |
| | | HELPER to GET UPLOADED FILES |
| | | *******************************************************************************/ |
| | | /** |
| | | * Get all files for a form (searches all upload fields within form) |
| | | */ |
| | | async getFilesForForm(formElement) { |
| | | const uploadFields = formElement.querySelectorAll('[data-upload-field]'); |
| | | const allFiles = []; |
| | | |
| | | for (const field of uploadFields) { |
| | | const fieldId = this.determineFieldId(field); |
| | | const files = await this.getFilesForField(fieldId); |
| | | allFiles.push(...files); |
| | | } |
| | | |
| | | return allFiles; |
| | | } |
| | | |
| | | /** |
| | | * Get all files for a specific field |
| | | */ |
| | | async getFilesForField(fieldId) { |
| | | const fieldData = this.getFieldData(fieldId); |
| | | if (!fieldData?.uploads) return []; |
| | | |
| | | const files = []; |
| | | const uploadsArray = fieldData.uploads instanceof Set |
| | | ? Array.from(fieldData.uploads) |
| | | : fieldData.uploads; |
| | | |
| | | for (const uploadId of uploadsArray) { |
| | | const upload = this.uploadStore.get(uploadId); |
| | | if (!upload) continue; |
| | | |
| | | // Get the actual File object from blob data |
| | | const file = await this.getBlobData(uploadId); |
| | | if (file) { |
| | | files.push({ |
| | | file: file, |
| | | uploadId: uploadId, |
| | | fieldName: fieldData.config.name, |
| | | meta: upload.meta || {} |
| | | }); |
| | | } |
| | | } |
| | | |
| | | return files; |
| | | } |
| | | |
| | | /******************************************************************************* |
| | | * RECOVERY & RESTORATION |
| | | *******************************************************************************/ |
| | | |
| | |
| | | async checkForStoredUploads() { |
| | | const allFieldStates = this.fieldStore.getAll(); |
| | | |
| | | console.log('Checking for stored uploads...', { |
| | | fieldStates: allFieldStates.length, |
| | | uploadStoreSize: this.uploadStore.data.size |
| | | }); |
| | | console.log(this.uploadStore.getAll()); |
| | | console.log(this.fieldStore.getAll()); |
| | | const pendingFields = allFieldStates.filter(field => { |
| | | if (!field.uploads) return false; |
| | | |
| | |
| | | ['completed', 'processed', 'local_processing', 'processed-original'].includes(upload.status); |
| | | }); |
| | | }); |
| | | console.log('Found pending fields:', pendingFields.length); |
| | | if (pendingFields.length === 0) return; |
| | | |
| | | this.showRecoveryNotification(pendingFields); |
| | | await this.showRecoveryNotification(pendingFields); |
| | | } |
| | | |
| | | async showRecoveryNotification(pendingFields) { |
| | |
| | | } |
| | | |
| | | // Initialize when DOM is ready |
| | | document.addEventListener('DOMContentLoaded', () => { |
| | | window.jvbUploads = new UploadManager(); |
| | | document.addEventListener('DOMContentLoaded', async function () { |
| | | window.auth.subscribe((event) => { |
| | | if (event === 'auth-loaded') { |
| | | window.jvbUploads = new UploadManager(); |
| | | } |
| | | }); |
| | | }); |