| | |
| | | this.queue.subscribe((event, operation) => { |
| | | if ((event === 'operation-status' || event === 'cancel-operation') |
| | | && ['image_upload', 'video_upload', 'document_upload'].includes(operation.type)) { |
| | | const data = operation.data instanceof FormData |
| | | ? this.stores.uploads.formDataToObject(operation.data) |
| | | : operation.data; |
| | | let uploadIds = []; |
| | | |
| | | let uploads = data['upload_ids']; |
| | | if (!uploads || uploads.length === 0) return; |
| | | if (event === 'cancel-operation') return this.handleOperationCancelled(uploads); |
| | | this.setBulkUpload(uploads, 'status', operation.status).then(()=>{}); |
| | | if (operation.data) { |
| | | // Handle FormData |
| | | if (operation.data instanceof FormData) { |
| | | const dataObj = this.stores.uploads.formDataToObject(operation.data); |
| | | uploadIds = dataObj['upload_ids'] || []; |
| | | } |
| | | // Handle regular object |
| | | else { |
| | | uploadIds = operation.data['upload_ids'] || []; |
| | | } |
| | | |
| | | if (operation.data['field_name'] !== '' && operation.data['item_id']) { |
| | | this.notify('upload_complete', { |
| | | field: operation.data['field_name'], |
| | | item_id: operation['item_id'] |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // If not in data, check result (for completed operations from backend) |
| | | if (uploadIds.length === 0 && operation.result && operation.result.upload_ids) { |
| | | uploadIds = operation.result.upload_ids; |
| | | } |
| | | |
| | | // Still no upload_ids? Log warning and bail |
| | | if (!uploadIds || uploadIds.length === 0) { |
| | | console.warn('[UploadManager] No upload_ids found for operation:', { |
| | | id: operation.id, |
| | | type: operation.type, |
| | | status: operation.status, |
| | | hasData: !!operation.data, |
| | | hasResult: !!operation.result |
| | | }); |
| | | return; |
| | | } |
| | | |
| | | // Handle cancellation |
| | | if (event === 'cancel-operation') { |
| | | return this.handleOperationCancelled(uploadIds); |
| | | } |
| | | |
| | | // Update upload status based on operation status |
| | | this.setBulkUpload(uploadIds, 'status', operation.status).then(() => { |
| | | // Log for debugging |
| | | console.log(`[UploadManager] Updated ${uploadIds.length} uploads to status: ${operation.status}`); |
| | | }); |
| | | |
| | | // Handle completion |
| | | if (operation.status === 'completed') { |
| | | uploads.forEach(upload => { |
| | | this.removeUpload(upload).then(()=>{}); |
| | | // For group uploads, mark as processed but keep for reference |
| | | if (operation.type === 'process_upload_groups') { |
| | | uploadIds.forEach(uploadId => { |
| | | this.setBulkUpload([uploadId], 'serverProcessed', true).then(() => {}); |
| | | }); |
| | | |
| | | // Log created posts if available |
| | | if (operation.result && operation.result.created_posts) { |
| | | console.log('[UploadManager] Created posts:', operation.result.created_posts); |
| | | } |
| | | |
| | | // Remove uploads after a delay to allow UI to update |
| | | setTimeout(() => { |
| | | uploadIds.forEach(uploadId => { |
| | | this.removeUpload(uploadId).then(() => {}); |
| | | }); |
| | | }, 2000); |
| | | } |
| | | // For direct uploads, remove immediately |
| | | else { |
| | | uploadIds.forEach(uploadId => { |
| | | this.removeUpload(uploadId).then(() => {}); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // Handle failures |
| | | if (operation.status === 'failed' || operation.status === 'failed_permanent') { |
| | | console.error('[UploadManager] Operation failed:', { |
| | | id: operation.id, |
| | | type: operation.type, |
| | | uploadIds: uploadIds, |
| | | error: operation.error_message |
| | | }); |
| | | } |
| | | } |
| | |
| | | fields: { |
| | | field: '[data-upload-field]', |
| | | input: 'input[type="file"]', |
| | | dropZone: '.file-upload-container', |
| | | dropZone: '.file-upload-wrapper', |
| | | preview: '.preview-wrap', |
| | | grid: '.item-grid.preview', |
| | | progress: { |
| | |
| | | } |
| | | } |
| | | |
| | | async queueUploads(endpoint, fieldId) { |
| | | async queueUploads(endpoint, fieldId, dependsOn = null) { |
| | | let data = new FormData(); |
| | | const field = this.fields.get(fieldId); |
| | | if (!field) return; |
| | |
| | | data.append('subtype', field.config.subtype); |
| | | data.append('item_id', field.config.itemID); |
| | | data.append('destination', field.config.destination); |
| | | if (dependsOn) { |
| | | data.append('depends_on', dependsOn); |
| | | } |
| | | } |
| | | |
| | | let posts, uploadMap, files; |
| | |
| | | if (details) { |
| | | details.open = false; |
| | | } |
| | | |
| | | |
| | | this.notify('groups_uploaded', { |
| | | fieldId: fieldId, |
| | | posts: posts, |
| | | content: field.config.content, |
| | | }); |
| | | } |
| | | if (operationId) { |
| | | field.operationId = operationId; |
| | |
| | | canMerge: mergable, |
| | | sendNow: endpoint === 'uploads/groups', |
| | | headers: { |
| | | 'action_nonce': window.auth.getNonce('dash') |
| | | 'X-Action-Nonce': window.auth.getNonce('dash') |
| | | }, |
| | | append: '_upload' |
| | | } |
| | |
| | | const fields = this.collectGroupFieldsFromDOM(groupElement, group.id); |
| | | |
| | | const post = { |
| | | groupId: group.id, |
| | | images: [], |
| | | fields: fields |
| | | }; |
| | |
| | | const remaining = uploads.filter(u => !u.group); |
| | | for (const upload of remaining) { |
| | | const post = { |
| | | groupId: window.generateID('group'), |
| | | images: [], |
| | | fields: {} |
| | | }; |
| | |
| | | if (!item) return; |
| | | |
| | | const uploadId = item.dataset.uploadId; |
| | | const attachmentId = item.dataset.id; |
| | | |
| | | if (!uploadId && !attachmentId) return; |
| | | if (!confirm('Remove this item?')) return; |
| | | await this.removeUpload(uploadId); |
| | | |
| | | if (uploadId) { |
| | | await this.removeUpload(uploadId); |
| | | } else { |
| | | const fieldId = this.getFieldIdFromElement(button); |
| | | item.remove(); |
| | | |
| | | if (fieldId) { |
| | | this.updateHiddenInput(fieldId); |
| | | this.maybeLockUploads(fieldId); |
| | | } |
| | | } |
| | | |
| | | this.a11y.announce('Item removed'); |
| | | } |
| | | |
| | | updateHiddenInput(fieldId) { |
| | | const field = this.fields.get(fieldId); |
| | | if (!field?.ui.hidden) return; |
| | | |
| | | const remaining = Array.from(field.ui.grid?.querySelectorAll(this.selectors.items.item) || []) |
| | | .map(el => el.dataset.id || el.dataset.uploadId) |
| | | .filter(Boolean); |
| | | |
| | | field.ui.hidden.value = remaining.join(','); |
| | | field.ui.hidden.dispatchEvent(new Event('change', { bubbles: true })); |
| | | } |
| | | async setBulkUpload(uploads, key, value) { |
| | | const promises = Array.from(uploads).map(async (upload) => { |
| | | if (typeof upload === 'string') upload = await this.stores.uploads.get(upload); |
| | |
| | | async removeUpload(uploadId) { |
| | | let upload = this.stores.uploads.get(uploadId); |
| | | if (!upload) return; |
| | | const fieldId = upload.field; // grab before clearing |
| | | |
| | | if (upload.group) { |
| | | let group = this.stores.groups.get(upload.group); |
| | | group.uploads = group.uploads.filter(id => id !== uploadId); |
| | |
| | | } |
| | | |
| | | await this.clearUpload(uploadId); |
| | | this.maybeLockUploads(upload.field); |
| | | this.updateHiddenInput(fieldId); |
| | | this.maybeLockUploads(fieldId); |
| | | |
| | | let handler = this.selectionHandlers.get(upload.field); |
| | | if (handler){ |
| | | let handler = this.selectionHandlers.get(fieldId); |
| | | if (handler) { |
| | | handler.deselect(uploadId); |
| | | } |
| | | |
| | |
| | | return; |
| | | } |
| | | |
| | | // Get current order from DOM |
| | | let items = Array.from(target.children) |
| | | .filter(el => el.matches(this.selectors.items.item) && !el.classList.contains('ghost')) |
| | | .map(upload => upload.dataset.uploadId) |
| | | .filter(id => id); |
| | | |
| | | if (!groupId) { |
| | | let hiddenInput = this.fields.get(fieldId)?.ui.hidden; |
| | | if (hiddenInput) { |
| | | hiddenInput.value = items.join(','); |
| | | } |
| | | this.updateHiddenInput(fieldId); |
| | | } else { |
| | | let items = Array.from(target.children) |
| | | .filter(el => el.matches(this.selectors.items.item) && !el.classList.contains('ghost')) |
| | | .map(upload => upload.dataset.uploadId) |
| | | .filter(id => id); |
| | | |
| | | let group = this.stores.groups.get(groupId); |
| | | if (group) { |
| | | group.uploads = items; |