// Site-wide file transfer manager for qualityiron.vegas - loaded once // via mainpagetemplate.jet (outside #content) so the floating panel and // any in-progress transfer survive spa.js's own #content-only swaps. // Adapted from barant.com's own documents.js (same proven mechanism), // just pointed at qualityiron's own /jobdocs/* routes instead of // barant.com's /documents/*. (function() { var panel = document.getElementById('filemgr-panel'); if (!panel) return; var toggle = document.getElementById('filemgr-toggle'); var badge = document.getElementById('filemgr-badge'); var list = document.getElementById('filemgr-list'); var clearBtn = document.getElementById('filemgr-clear'); var transfers = {}; var nextId = 1; function escapeHtml(s) { var div = document.createElement('div'); div.textContent = s == null ? '' : String(s); return div.innerHTML; } function updateBadge() { var activeCount = 0; for (var k in transfers) { if (transfers[k].status === 'active') activeCount++; } badge.textContent = activeCount > 0 ? String(activeCount) : ''; } function removeEmptyPlaceholder() { var empty = document.getElementById('filemgr-empty'); if (empty) empty.remove(); } function showEmptyPlaceholderIfNeeded() { if (Object.keys(transfers).length === 0 && !document.getElementById('filemgr-empty')) list.innerHTML = '
No transfers yet.
'; } var UPLOAD_ICON = ''; var DOWNLOAD_ICON = ''; function addTransfer(name, direction) { var id = 'xfer' + (nextId++); removeEmptyPlaceholder(); var el = document.createElement('div'); el.className = 'filemgr-item'; el.innerHTML = '
' + '' + (direction === 'upload' ? UPLOAD_ICON : DOWNLOAD_ICON) + '' + '' + escapeHtml(name) + '' + '0%' + '
' + '
'; list.insertBefore(el, list.firstChild); transfers[id] = { el: el, status: 'active' }; panel.classList.add('filemgr-expanded'); updateBadge(); return id; } function updateProgress(id, percent) { var t = transfers[id]; if (!t) return; t.el.querySelector('.filemgr-progress-fill').style.width = percent + '%'; t.el.querySelector('.filemgr-item-status').textContent = percent + '%'; } function markDone(id, statusText) { var t = transfers[id]; if (!t) return; t.status = 'done'; t.el.classList.add('filemgr-done'); t.el.querySelector('.filemgr-progress-fill').style.width = '100%'; t.el.querySelector('.filemgr-item-status').textContent = statusText || 'Done'; updateBadge(); } function markFailed(id, statusText) { var t = transfers[id]; if (!t) return; t.status = 'failed'; t.el.classList.add('filemgr-failed'); t.el.querySelector('.filemgr-item-status').textContent = statusText || 'Failed'; updateBadge(); } // Uploads a FileList/array of File objects, each as its own // concurrent XHR request, tracked here as separate panel entries. // job_id/subtype_id come from data attributes on the dropzone element // itself (set by jobdocs.page per the job/category being uploaded // into) rather than being hardcoded here, since this same script is // shared across every job's documents page. window.fileManagerUpload = function(files, jobId, subtypeId) { for (var i = 0; i < files.length; i++) { (function(file) { var id = addTransfer(file.name, 'upload'); var xhr = new XMLHttpRequest(); transfers[id].xhr = xhr; xhr.open('POST', '/jobdocs/api/upload'); xhr.upload.addEventListener('progress', function(e) { if (e.lengthComputable) updateProgress(id, Math.round((e.loaded / e.total) * 100)); }); xhr.addEventListener('load', function() { var res = null; try { res = JSON.parse(xhr.responseText); } catch (e) {} if (xhr.status === 200 && res && res.ok) { markDone(id); document.dispatchEvent(new CustomEvent('jobdocs:changed')); } else { markFailed(id, (res && res.error) || 'Failed'); } }); xhr.addEventListener('error', function() { markFailed(id); }); var fd = new FormData(); fd.append('name', file.name); fd.append('job_id', jobId); fd.append('subtype_id', subtypeId); fd.append('mimetype', file.type || 'application/octet-stream'); fd.append('file_size', file.size); fd.append('filedata', file); xhr.send(fd); })(files[i]); } }; // Downloads a document by id via XHR (responseType 'blob') so it gets // real tracked progress, then hands the bytes to the browser's normal // save flow via a Blob URL + synthetic click. window.fileManagerDownload = function(id, name) { var xferId = addTransfer(name, 'download'); var xhr = new XMLHttpRequest(); transfers[xferId].xhr = xhr; xhr.open('GET', '/jobdocs/download?id=' + encodeURIComponent(id)); xhr.responseType = 'blob'; xhr.addEventListener('progress', function(e) { if (e.lengthComputable) updateProgress(xferId, Math.round((e.loaded / e.total) * 100)); }); xhr.addEventListener('load', function() { if (xhr.status !== 200) { markFailed(xferId); return; } var blobUrl = URL.createObjectURL(xhr.response); var a = document.createElement('a'); a.href = blobUrl; a.download = name; document.body.appendChild(a); a.click(); document.body.removeChild(a); setTimeout(function() { URL.revokeObjectURL(blobUrl); }, 30000); markDone(xferId); }); xhr.addEventListener('error', function() { markFailed(xferId); }); xhr.send(); }; toggle.addEventListener('click', function() { panel.classList.toggle('filemgr-expanded'); }); clearBtn.addEventListener('click', function() { for (var k in transfers) { if (transfers[k].status !== 'active') { transfers[k].el.remove(); delete transfers[k]; } } showEmptyPlaceholderIfNeeded(); }); })();