| | |
| | | storeName: 'queue', |
| | | keyPath: 'id', |
| | | endpoint: this.endpoint, |
| | | TTL: Infinity, |
| | | // TTL: Infinity, |
| | | TTL: 5000, |
| | | indexes: [ |
| | | {name: 'status', keyPath: 'status'}, |
| | | {name: 'type', keyPath: 'type'}, |
| | |
| | | }, |
| | | body: JSON.stringify({ |
| | | action, |
| | | ids: statusOrId, |
| | | ids: Array.isArray(statusOrId) ? statusOrId : [statusOrId], |
| | | user: this.user |
| | | }) |
| | | } |
| | |
| | | item.ui.actions.refresh.hidden = op.status !== 'completed'; |
| | | } |
| | | } |
| | | getProgress(op) { |
| | | if (op.progress) return op.progress; |
| | | if (!this.statuses.includes(op.status)) return 0; |
| | | let statusProgress = { |
| | | 'queued': 10, |
| | | 'uploading': 25, |
| | | 'pending': 40, |
| | | 'processing':70, |
| | | 'completed':100, |
| | | 'failed':0, |
| | | 'failed_permanent':0 |
| | | }; |
| | | return statusProgress[op.status]??0; |
| | | getProgress(op) { |
| | | // Check server-provided percentage first |
| | | if (op.progress_percentage !== undefined) { |
| | | return op.progress_percentage; |
| | | } |
| | | // Legacy: check old 'progress' field |
| | | if (op.progress !== undefined) { |
| | | return op.progress; |
| | | } |
| | | // Fallback to status-based calculation |
| | | if (!this.statuses.includes(op.status)) return 0; |
| | | const statusProgress = { |
| | | 'queued': 10, |
| | | 'uploading': 25, |
| | | 'pending': 40, |
| | | 'processing': 70, |
| | | 'completed': 100, |
| | | 'failed': 0, |
| | | 'failed_permanent': 0 |
| | | }; |
| | | return statusProgress[op.status] ?? 0; |
| | | } |
| | | removeOperationUI(opId) { |
| | | let op = this.items.get(opId); |
| | | if (!op) return; |
| | |
| | | 'processing': 'Processing', |
| | | 'completed': 'Completed', |
| | | 'failed': 'Failed', |
| | | 'failed_permanent': 'Failed permanently' |
| | | 'failed_permanent': 'Failed permanently', |
| | | 'merged': 'Merged' |
| | | }; |
| | | return labels[status]; |
| | | } |
| | |
| | | case 'pending': |
| | | return item.position ? `Position ${item.position} in queue` : 'In server queue'; |
| | | case 'processing': |
| | | return item.progress ? `${item.progress}% complete` : 'Processing...'; |
| | | // Show progress count if available |
| | | if (item.count && item.progress_count !== undefined) { |
| | | const processed = item.progress_count; |
| | | const total = item.count; |
| | | const percentage = Math.round((processed / total) * 100); |
| | | return `Processing ${processed}/${total} items (${percentage}%)`; |
| | | } |
| | | // Fallback to percentage only |
| | | if (item.progress_percentage !== undefined) { |
| | | return `${item.progress_percentage}% complete`; |
| | | } |
| | | return 'Processing...'; |
| | | case 'completed': |
| | | return 'Successfully completed. Refresh to see changes.'; |
| | | case 'merged': |
| | | return item.merged_into |
| | | ? `Merged with another operation (${item.merged_into.substring(0, 8)}...)` |
| | | : 'Merged with another operation'; |
| | | case 'failed': |
| | | return `Failed: ${item.lastError || 'Unknown error'} (Retry ${item.retries}/${2})`; |
| | | case 'failed_permanent': |
| | |
| | | |
| | | // If we have local operation data, preserve it |
| | | if (localOp && localOp.endpoint) { |
| | | return { |
| | | const mappedOp = { |
| | | ...localOp, |
| | | ...serverOp, |
| | | endpoint: localOp.endpoint, |
| | | method: localOp.method, |
| | | headers: localOp.headers, |
| | | progress_percentage: serverOp.progress_percentage, |
| | | progress_count: serverOp.progress_count, |
| | | count: serverOp.count |
| | | }; |
| | | |
| | | if (serverOp.merged_into) { |
| | | this.handleMergedOperation(mappedOp); |
| | | } |
| | | } |
| | | |
| | | |
| | | // Minimal mapping for server-only operations |
| | | // Extract endpoint from type if possible, otherwise use type |
| | | const endpoint = serverOp.type ? serverOp.type.replace('_update', '').replace('_', '/') : 'unknown'; |
| | | |
| | | return { |
| | | const mappedOp = { |
| | | ...serverOp, |
| | | endpoint: endpoint, |
| | | method: 'POST', |
| | | headers: { ...this.headers }, |
| | | }; |
| | | if (serverOp.merged_into) { |
| | | this.handleMergedOperation(mappedOp); |
| | | } |
| | | return mappedOp |
| | | } |
| | | |
| | | /** |
| | | * Handle merged operations |
| | | * The target operation already has merged data from server, |
| | | * so we just need to clean up the merged operation locally |
| | | */ |
| | | handleMergedOperation(operation) { |
| | | if (!operation.merged_into) return; |
| | | |
| | | console.log(`[Queue] Operation ${operation.id} merged into ${operation.merged_into}`); |
| | | |
| | | // Auto-dismiss merged operation after brief display |
| | | // The target operation already has all the merged data from server |
| | | setTimeout(() => { |
| | | this.store.delete(operation.id); |
| | | this.removeOperationFromUI(operation.id); |
| | | }, 3000); |
| | | } |
| | | |
| | | /**************************************************************************** |