Jake Vanderwerf
2025-09-30 2143fc670fa245750ac7f774fcb38ea8cfbf7edb
1
(()=>{class e{constructor(){this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.notifications=window.jvbNotifications,this.initDB(),this.fields=new Map,this.uploads=new Map,this.uploadBlobs=new Map,this.timeouts=new Map,this.selected=new Map,this.worker={worker:null,timeout:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:1e4,batchSize:1,maxConcurrent:3,restartAfterTimeout:!0}},this.touch={x:null,y:null},this.hasBulkContext=null!==document.querySelector("details.uploader"),this.isTouching=!1,this.groups=new Map,this.subscribers=new Set,this.settings={allowedTypes:["image/jpeg","image/png","image/gif","image/webp","image/avif"],maxFileSize:5242880,maxProcessingTime:12e4,processingCheckInterval:5e3,smartCompression:!0,fieldTypes:{single:{maxFiles:1,allowMultiple:!1},gallery:{maxFiles:20,allowMultiple:!0},groupable:{maxFiles:20,allowMultiple:!0}}},this.statusMapping={received:"Image Received",local_processing:"Processing Image...",queued:"Waiting to upload...",uploading:"Uploading to Server",pending:"Successfully sent to server. In line for further processing.",processing:"Processing on server...",completed:"Upload complete!",failed:"Upload failed (will retry)",failed_permanent:"Upload failed permanently"},this.init()}async init(){this.initElements(),this.initListeners(),this.initCompressionWorker(),this.queue.subscribe(((e,t)=>{if(console.log("Operation Endpoint: ",t.endpoint),"uploads"===t.endpoint)switch(e){case"cancel-operation":this.clearField(t.data.get("field_key"));break;case"operation-status":console.log("Operation Data: ",t.data);const e=t.data?.field_key||(t.data instanceof FormData?t.data.get("field_key"):null);e&&(console.log("Updating field status:",e,t.status),this.updateFieldStatus(e,t.status))}})),await this.checkPendingUploads(),this.scanFields()}initElements(){this.selectors={field:{field:".field.image",dropZone:".file-upload-container",preview:".item-grid.preview",hiddenValue:'input[type="hidden"]',progress:{progress:".progress",details:".progress .details",fill:".progress .fill",count:".progress .count"}},item:{img:"img",progress:{progress:".progress",details:".progress .details",fill:".progress .fill",count:".progress .count"},status:".status",select:'[name*="select-item"]',actions:".item-actions",featured:'[name="featured"]',meta:".upload-meta"},groups:{container:".item-grid.groups",display:".group-display",selectAll:"#select-all-uploads",actions:".selection-actions",info:".selection-controls .info",count:".selection-count",group:".upload-group",empty:".empty-group"}},this.ui={}}scanFields(){document.querySelectorAll(this.selectors.field.field).forEach((e=>{this.registerUploader(e)}))}registerUploader(e,t={}){let a=e.dataset.uploader??this.determineKey(e);if(e.dataset.uploader=a,!this.fields.has(a)){let s=e.dataset.type,i=this.settings.fieldTypes[s]??this.settings.fieldTypes.single,r={key:a,name:e.dataset.field,ui:{},type:s,maxFiles:i.maxFiles,multiple:i.allowMultiple,content:e.dataset.content??e.closest("dialog")?.dataset.content??e.closest("form").dataset.save??!1,itemID:e.dataset.itemID??e.closest("dialog")?.dataset.itemID??!1,context:e.dataset.context??e.closest("dialog")?.dataset.context??!1,mode:e.dataset.mode??"direct",...t};r.ui=window.uiFromSelectors(this.selectors,e),r.ui.groups.groups=new Map,this.selected.set(a,new Set),this.fields.set(a,r),"groupable"!==r.type||this.hasGroups||this.initGroupListeners()}return a}determineKey(e){return`${e.dataset.content??e.closest("dialog")?.dataset.content??e.closest("form").dataset.save??""}_${e.dataset.itemID??e.closest("dialog")?.dataset.itemID??""}_${e.dataset.field}`}getFieldIdFromElement(e){let t=e.closest(".field.image");if(t)return t.dataset.uploader??this.determineKey(t)}getFieldFromElement(e){let t=this.getFieldIdFromElement(e);return!!this.fields.has(t)&&this.fields.get(t)}getUploadFromElement(e){let t=this.getUploadIdFromElement(e);return!!this.uploads.has(t)&&this.uploads.get(t)}getUploadIdFromElement(e){let t=e.closest("[data-upload-id]");return t?.dataset.uploadId||null}getGroupFromElement(e){let t=this.getGroupIdFromElement(e);return!!this.groups.has(t)&&this.groups.get(t)}getGroupIdFromElement(e){return e.dataset.groupId??e.closest("[data-group-id]")?.dataset.groupId??e.closest(":has([data-group-id])")?.querySelector("[data-group-id]")?.dataset.groupId??null}getModalType(e){if(!(e&&e.ui&&e.ui.field&&e.ui.field.field))return null;const t=e.ui.field.field.closest("dialog");return t?t.classList.contains("edit")?"edit":t.classList.contains("create")?"create":t.classList.contains("bulkEdit")?"bulkEdit":t.className:null}getStatusText(e){return this.statusMapping[e]||e}getStatusIcon(e){return window.getIcon(this.queue.icons[e])}getStatusProgress(e){switch(console.log("Getting status progress for: ",e),e){case"local_processing":return 28;case"queued":return 50;case"uploading":return 66;case"pending":return 75;case"processing":return 89;case"completed":return 100;default:return 0}}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),this.hasBulkContext&&(this.pasteHandler=this.handlePaste.bind(this),document.addEventListener("paste",this.pasteHandler)),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),window.addEventListener("beforeunload",this.handleBeforeUnload.bind(this))}clearListeners(){document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),this.hasBulkContext&&document.removeEventListener("paste",this.pasteHandler)}initGroupListeners(){this.hasGroups=!0,this.dragStartHandler=this.handleDragStart.bind(this),this.dragEndHandler=this.handleDragEnd.bind(this),this.dragEnterHandler=this.handleDragEnter.bind(this),this.dragOverHandler=this.handleDragOver.bind(this),this.dragLeaveHandler=this.handleDragLeave.bind(this),this.dropHandler=this.handleDrop.bind(this),this.touchStartHandler=this.handleTouchStart.bind(this),this.touchMoveHandler=this.handleTouchMove.bind(this),this.touchEndHandler=this.handleTouchEnd.bind(this),this.touchCancelHandler=this.handleTouchCancel.bind(this),document.addEventListener("dragstart",this.dragStartHandler),document.addEventListener("dragend",this.dragEndHandler),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("drop",this.dropHandler),document.addEventListener("touchstart",this.touchStartHandler),document.addEventListener("touchmove",this.touchMoveHandler),document.addEventListener("touchend",this.touchEndHandler),document.addEventListener("touchcancel",this.touchCancelHandler)}clearGroupListeners(){document.removeEventListener("dragstart",this.dragStartHandler),document.removeEventListener("dragend",this.dragEndHandler),document.removeEventListener("dragenter",this.dragEnterHandler),document.removeEventListener("dragover",this.dragOverHandler),document.removeEventListener("dragleave",this.dragLeaveHandler),document.removeEventListener("drop",this.dropHandler),document.removeEventListener("touchstart",this.touchStartHandler),document.removeEventListener("touchmove",this.touchMoveHandler),document.removeEventListener("touchend",this.touchEndHandler),document.removeEventListener("touchcancel",this.touchCancelHandler)}handleClick(e){if(e.target.closest(this.selectors.field.field))if(window.targetCheck(e,".restart-uploads")){e.preventDefault();const t=this.getFieldIdFromElement(e.target);this.restartUploads(t)}else if(window.targetCheck(e,".dismiss-cache-restore")){e.preventDefault();const t=e.target.closest(".upload-recovery-notification");t&&t.remove()}else if(window.targetCheck(e,"#select-all-uploads"))e.preventDefault(),this.handleSelectAll(e.target);else if(window.targetCheck(e,".upload-select")){e.shiftKey&&this.lastClickedUpload?(e.preventDefault(),this.handleRangeSelection(e.target,e)):this.updateSelection(e)}else if(window.targetCheck(e,".create-from-selection")){e.preventDefault();let t=this.createGroup(this.getFieldFromElement(e.target));this.addSelectionToGroup(t)}else if(window.targetCheck(e,".remove-selection"))e.preventDefault(),this.removeSelection(e.target);else if(window.targetCheck(e,".add-to-group, .add-selection-to-group"))e.preventDefault(),this.addSelectionToGroup(e.target);else if(window.targetCheck(e,".remove-group")){e.preventDefault();const t=e.target.closest(".upload-group");if(t){this.getFieldFromElement(t);this.removeGroup(t,!0)}}else if(window.targetCheck(e,".remove")){e.preventDefault();const t=this.getUploadIdFromElement(e.target),a=this.getFieldIdFromElement(e.target);t&&a&&this.removeUpload(a,t)}else if(window.targetCheck(e,".submit-uploads")){e.preventDefault();const t=this.getFieldIdFromElement(e.target);this.submitUploads(t)}else if(window.targetCheck(e,".retry-upload")){e.preventDefault();const t=this.getUploadIdFromElement(e.target);this.retryUpload(t)}}handleChange(e){if(e.target.closest(this.selectors.field.field)&&!e.target.classList.contains(this.selectors.field.hiddenValue))if(e.preventDefault(),window.targetCheck(e,'[type="file"]')){console.log(this.fields);let t=this.getFieldFromElement(e.target);if(console.log(t),!t)return void console.warn("File change on unregistered field: ",t.key);const a=Array.from(e.target.files);if(0===a.length)return;this.processFiles(t.key,a),e.target.value=""}else if(e.target.name.includes("select-"))this.updateSelection(e);else if(e.target.closest(".upload-meta")){e.preventDefault();let t=e.target.name,a=e.target.value,s=this.getUploadFromElement(e.target);s.changes[t]=a,this.uploads.set(s.id,s),this.persistFieldState(s.fieldId)}else if(e.target.closest(".group.fields")){let t=this.getGroupFromElement(e.target),a=e.target.name;t.changes[a]=e.target.value,this.persistFieldState(t.fieldId),this.groups.set(t.id,t)}}handlePaste(e){window.debouncer.schedule("imagePaste",(()=>{const t=Array.from(e.clipboardData.items).filter((e=>e.type.startsWith("image/")));if(0===t.length)return;e.preventDefault();const a=this.getFieldIdFromElement(e.target);if(!a)return;const s=[];t.forEach(((e,t)=>{const a=e.getAsFile();if(a){const e=new File([a],`pasted_image_${t+1}.png`,{type:a.type,lastModified:Date.now()});s.push(e)}})),s.length>0&&this.processFiles(a,s)}),100)}isTouchOnFormElement(e){return["input","button","label","select","textarea"].some((t=>e.matches(t)||e.closest(t)))}startDragOperation(e){const{primaryElement:t,sourceType:a,startPosition:s,event:i}=e,r=this.getUploadIdFromElement(t),o=this.getFieldIdFromElement(t),n=this.getDraggedItems(t);this.dragState={primaryItem:r,draggedItems:n,isDragging:!0,isMultiDrag:n.length>1,fieldId:o,sourceType:a,startTime:Date.now(),startPosition:s,currentPosition:s,currentTarget:null,validTarget:null,dragPreview:null,touchId:"touch"===a?i.touches[0]?.identifier:null,touchMoved:!1},this.createDragPreview(t),this.applyDraggingState(!0);const l=this.dragState.isMultiDrag?`Started dragging ${n.length} items`:"Started dragging item";return this.a11y.announce(l),this.provideDragFeedback("start"),!0}updateDragOperation(e,t){if(!this.dragState.isDragging)return;const{sourceType:a,startPosition:s}=this.dragState;if(this.dragState.currentPosition=e,"touch"===a&&!this.dragState.touchMoved){const t=Math.abs(e.x-s.x),a=Math.abs(e.y-s.y);(t>10||a>10)&&(this.dragState.touchMoved=!0)}this.updateDragPreview(e),this.updateDropTarget(t)}endDragOperation(e=null){if(!this.dragState.isDragging)return;const t=("drag"===this.dragState.sourceType||this.dragState.touchMoved)&&this.dragState.validTarget;t&&this.dragState.validTarget&&this.processItemDrop({itemIds:this.dragState.draggedItems,targetElement:this.dragState.validTarget,fieldId:this.dragState.fieldId,dropType:this.dragState.isMultiDrag?"multiple":"single",sourceType:this.dragState.sourceType}),this.cleanupDragOperation();const a=t?this.dragState.isMultiDrag?`Moved ${this.dragState.draggedItems.length} items`:"Item moved":"Drag cancelled";this.a11y.announce(a)}processItemDrop(e){const{itemIds:t,targetElement:a,fieldId:s,dropType:i,sourceType:r}=e;if(!t?.length||!a||!s)return!1;let o=a.classList.contains("item-grid")&&a.classList.contains("preview"),n=a;if(a.classList.contains("empty-group")){let e=this.createGroup(s);n=e.querySelector(".item-grid"),o=!1}if(t.forEach((e=>{this.addImageToGroup(e,n,o)})),"multiple"===i){const e=this.fields.get(s);this.clearAllSelections(e)}const l="multiple"===i?`Moved ${t.length} images to ${o?"main area":"group"}`:"Image moved to "+(o?"main area":"group");return this.a11y.announce(l),this.provideFeedback(r,"success",{count:t.length,isMultiple:"multiple"===i}),!0}clearAllSelections(e){if(e.container.querySelectorAll('[name*="select-item"]').forEach((e=>{e.checked=!1})),e.selectAll){e.selectAll.checked=!1;const t=e.selectAll.nextElementSibling;t&&(t.textContent="Select All")}e.selectActions&&(e.selectActions.hidden=!0),e.selectInfo&&(e.selectInfo.hidden=!0)}cleanupDragOperation(){this.dragState.dragPreview&&this.dragState.dragPreview.remove(),this.applyDraggingState(!1),this.clearDropTargetStates(),this.dragState.isDragging=!1,this.dragState.dragPreview=null,this.dragState.draggedItems=[]}getDraggedItems(e){const t=this.getSelectedUploads(e),a=e.dataset.uploadId;return t.length>1&&t.includes(a)?t:[a]}applyDraggingState(e){this.dragState.draggedItems.forEach((t=>{const a=document.querySelector(`[data-upload-id="${t}"]`);a&&a.classList.toggle("dragging",e)}))}createDragPreview(e){const{isMultiDrag:t,draggedItems:a}=this.dragState;this.dragState.dragPreview=t?this.createMultiDragPreview(e,a):this.createSingleDragPreview(e),this.updateDragPreview(this.dragState.startPosition),document.body.appendChild(this.dragState.dragPreview)}createSingleDragPreview(e){const t=e.cloneNode(!0);return t.dataset.uploadId=t.dataset.uploadId+"-dragging",this.styleDragPreview(t,!1),t}styleDragPreview(e,t=!1){e.style.cssText=`\n        position: fixed;\n        z-index: 10000;\n        pointer-events: none;\n        opacity: 0.9;\n        transform: scale(1.05);\n        transition: transform 0.2s ease;\n        ${t?"\n            width: 120px;\n            height: 120px;\n            background: white;\n            border-radius: 8px;\n            box-shadow: 0 8px 32px rgba(0,0,0,0.3);\n            padding: 4px;\n        ":"\n            border-radius: 4px;\n            box-shadow: 0 4px 16px rgba(0,0,0,0.2);\n        "}\n    `,e.classList.add("drag-preview","is-dragging"),t&&e.classList.add("multi-item")}createMultiDragPreview(e,t){const a=document.createElement("div");a.className="drag-preview multi-item";const s=Math.min(t.length,3);for(let e=0;e<s;e++){const s=t[e],i=document.querySelector(`[data-upload-id="${s}"]`);if(i){const t=i.cloneNode(!0);t.dataset.uploadId=s+"_dragging",t.style.cssText=`\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: ${4*e}px;\n\t\t\t\tleft: ${4*e}px;\n\t\t\t\twidth: calc(100% - ${4*e}px);\n\t\t\t\theight: calc(100% - ${4*e}px);\n\t\t\t\topacity: ${1-.15*e};\n\t\t\t\ttransform: rotate(${2*(e-1)}deg);\n\t\t\t\tz-index: ${10-e};\n\t\t\t\tborder-radius: 4px;\n\t\t\t\toverflow: hidden;\n\t\t\t`,a.appendChild(t)}}if(t.length>1){const e=this.createCountBadge(t.length);a.appendChild(e)}return this.styleDragPreview(a,!0),a}updateDragPreview(e){if(!this.dragState.dragPreview)return;let t;t="touch"===this.dragState.sourceType?this.dragState.isMultiDrag?{x:-60,y:-80}:{x:-50,y:-60}:this.dragState.isMultiDrag?{x:15,y:15}:{x:10,y:10};const a=e.x-this.dragState.startPosition.x,s=e.y-this.dragState.startPosition.y;this.dragState.dragPreview.style.transform=`translate(${a+t.x}px, ${s+t.y}px) scale(1.05)`}updateDropTarget(e){this.dragState.currentTarget&&this.clearDropTargetState(this.dragState.currentTarget);const t=this.findValidDropTarget(e);if(this.dragState.currentTarget=e,this.dragState.validTarget=t,t&&(this.applyDropTargetState(t),"touch"===this.dragState.sourceType&&navigator.vibrate)){const e=this.dragState.isMultiDrag?[25,10,25]:[25];navigator.vibrate(e)}}findValidDropTarget(e){if(!e)return null;const t=e.closest(".item-grid.group, .empty-group, .item-grid.preview");if(t){if(this.getFieldIdFromElement(t)===this.dragState.fieldId)return t}return null}applyDropTargetState(e){e.classList.add("dragover"),this.dragState.isMultiDrag&&(e.classList.add("multi-drop"),e.setAttribute("data-item-count",this.dragState.draggedItems.length))}clearDropTargetState(e){e.classList.remove("dragover","multi-drop"),e.removeAttribute("data-item-count")}clearDropTargetStates(){document.querySelectorAll(".dragover").forEach((e=>{e.classList.remove("dragover","multi-drop"),e.removeAttribute("data-item-count")}))}createCountBadge(e){const t=document.createElement("div");return t.className="selection-count-badge",t.textContent=e.toString(),t.style.cssText="\n\t\t\tposition: absolute;\n\t\t\ttop: -8px;\n\t\t\tright: -8px;\n\t\t\tbackground: var(--accent-primary);\n\t\t\tcolor: white;\n\t\t\tborder-radius: 50%;\n\t\t\twidth: 24px;\n\t\t\theight: 24px;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tfont-size: 12px;\n\t\t\tfont-weight: bold;\n\t\t\tbox-shadow: 0 2px 8px rgba(0,0,0,0.3);\n\t\t\tz-index: 20;\n\t\t",t}provideDragFeedback(e){const t={start:[50],success:this.dragState.isMultiDrag?[50,25,50,25,50]:[50,25,50],cancel:[100]};"touch"===this.dragState.sourceType&&navigator.vibrate&&t[e]&&navigator.vibrate(t[e])}provideFeedback(e,t,a={}){const s={success:a.isMultiple?[50,25,50,25,50]:[50,25,50],error:[100,50,100]};"touch"===e&&navigator.vibrate&&s[t]&&navigator.vibrate(s[t])}clearDragoverStates(){document.querySelectorAll(".dragover").forEach((e=>{e.classList.remove("dragover","multi-drop"),e.removeAttribute("data-item-count")}))}handleDragEnter(e){if(window.targetCheck(e,".image.field")&&e.dataTransfer.types.includes("Files")){e.preventDefault();const t=e.target.closest(".file-upload-container");t&&t.classList.add("dragover")}}handleDragLeave(e){if(!window.targetCheck(e,".image.field"))return;const t=e.target.closest(".file-upload-container");t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleDragStart(e){if(!window.targetCheck(e,".image.field"))return;const t=e.target.closest("[data-upload-id]");if(!t)return;this.startDragOperation({primaryElement:t,sourceType:"drag",startPosition:{x:e.clientX,y:e.clientY},event:e})?(e.dataTransfer.setData("text/plain",this.dragState.primaryItem),e.dataTransfer.effectAllowed="move"):e.preventDefault()}handleDragOver(e){this.dragState.isDragging&&window.targetCheck(e,".image.field")&&(e.preventDefault(),this.updateDragOperation({x:e.clientX,y:e.clientY},e.target))}handleDrop(e){if(!window.targetCheck(e,".image.field"))return;e.preventDefault(),this.clearDragoverStates();const t=e.target.closest(".file-upload-container");if(t){const a=Array.from(e.dataTransfer.files);if(a.length>0){const e=this.getFieldIdFromElement(t);e&&(this.processFiles(e,a),this.a11y.announce(`${a.length} file(s) dropped for upload`))}}}handleDragEnd(e){if(!this.dragState.isDragging)return;const t=document.elementFromPoint(this.dragState.currentPosition?.x||e.clientX,this.dragState.currentPosition?.y||e.clientY);this.endDragOperation(t)}handleTouchStart(e){if(!window.targetCheck(e,".image.field"))return;if(this.isTouchOnFormElement(e.target))return;const t=e.target.closest("[data-upload-id]");if(!t)return;const a=e.touches[0];this.startDragOperation({primaryElement:t,sourceType:"touch",startPosition:{x:a.clientX,y:a.clientY},event:e})&&e.preventDefault()}handleTouchMove(e){if(!this.dragState.isDragging)return;e.preventDefault();const t=e.touches[0],a=document.elementFromPoint(t.clientX,t.clientY);this.updateDragOperation({x:t.clientX,y:t.clientY},a)}handleTouchEnd(e){if(!this.dragState.isDragging)return;e.preventDefault();const t=e.changedTouches[0],a=document.elementFromPoint(t.clientX,t.clientY);this.endDragOperation(a)}handleTouchCancel(e){this.dragState.isDragging&&(this.cleanupDragOperation(),this.a11y.announce("Drag cancelled"))}async submitUploads(e){const t=this.fields.get(e);if(!t)return;const a=Array.from(t.uploads||[]).map((e=>this.uploads.get(e))).filter((e=>e&&("processed"===e.status||"processed-original"===e.status)));if(0!==a.length)try{await this.queueUpload(e),this.notifications.add(`Submitting ${a.length} upload(s)`,"info")}catch(t){this.error.log(t,{component:"UploadManager",action:"submitUploads",fieldId:e}),this.notifications.add("Failed to submit uploads","error")}else this.notifications.add("No uploads ready to submit","warning")}async retryUpload(e){const t=this.uploads.get(e);if(!t)return;if(this.fields.get(t.fieldId))try{if(this.updateUploadStatus(e,"received"),t.processedFile)this.updateUploadStatus(e,"processed"),await this.queueUpload(t.fieldId);else{if(!t.originalFile)throw new Error("No file data available for retry");await this.processFile(t.fieldId,t.originalFile)&&await this.queueUpload(t.fieldId)}this.notifications.add("Retrying upload...","info")}catch(t){this.error.log(t,{component:"UploadManager",action:"retryUpload",uploadId:e}),this.notifications.add("Failed to retry upload","error")}}async restartUploads(e){const t=this.fields.get(e);if(!t?.uploads)return;const a=Array.from(t.uploads).map((e=>this.uploads.get(e))).filter((e=>e&&"failed"===e.status));if(0!==a.length){for(const e of a)await this.retryUpload(e.id);this.notifications.add(`Restarting ${a.length} upload(s)`,"info")}else this.notifications.add("No failed uploads to restart","info")}async queueUpload(e){const t=this.fields.get(e);if(!t?.uploads)return;const a=Array.from(t.uploads);if(0===a.length)return;const s=this.prepareUploadData(t,a);this.a11y.announce("Queuing for upload");let i=1===a.length?"image":"images";const r={endpoint:"uploads",method:"POST",data:s,title:`Uploading ${a.length} ${i} to server...`,popup:`Uploading ${a.length} ${i}...`,canMerge:!1,headers:{action_nonce:jvbSettings.dash},append:"_upload"};try{const e=await this.queue.addToQueue(r);return a.forEach((t=>{let a=this.uploads.get(t);a&&(a.operationId=e,this.updateUploadStatus(t,"queued"))})),t.operationId=e,e}catch(e){throw e}finally{this.persistFieldState(t.key)}}prepareUploadData(e,t){console.log("Preparing Upload:",e);const a=new FormData;a.append("content",e.content),a.append("mode",e.mode),a.append("field_name",e.name),a.append("field_key",e.key),a.append("field_type",e.type),a.append("item_id",e.itemID),a.append("context",e.context);let s=[];t.forEach((e=>{let t=this.uploads.get(e);if(t){const i=t.processedFile||t.originalFile;i?(a.append("files[]",i),s.push(t.id)):console.warn(`No file for upload ${e}`)}else console.warn(`Upload ${e} not found in uploads map`)})),a.append("upload_map",s),console.log("Final FormData:");for(let e of a.entries())console.log(e[0],e[1]);return a}async queueImageMeta(e){const t=this.getUploadFromElement(element);if(!t)return;const a=this.fields.get(t.fieldId);if(!a)return;const s=element.closest(".upload-meta");if(!s)return;const i={title:s.querySelector('[name="title"]')?.value||"",alt_text:s.querySelector('[name="alt_text"]')?.value||"",caption:s.querySelector('[name="caption"]')?.value||"",description:s.querySelector('[name="description"]')?.value||""};t.meta={...t.meta,...i},this.uploads.set(t.id,t),this.hasMetaChanges=!0;"completed"===t.status&&t.attachmentId?await this.sendMetaUpdate(t):t.operationId?this.queueDependentMetaUpdate(t):this.persistFieldState(a.key)}async sendMetaUpdate(e){const t=new FormData;t.append("attachment_id",e.attachmentId),t.append("title",e.meta.title),t.append("alt_text",e.meta.alt_text),t.append("caption",e.meta.caption),t.append("description",e.meta.description);const a={endpoint:"uploads/meta",method:"POST",data:t,title:`Updating metadata for ${e.meta.originalName}`,canMerge:!0,headers:{action_nonce:jvbSettings.dash}};try{await this.queue.addToQueue(a),this.notifications.add("Metadata updated","success")}catch(t){this.error.log(t,{component:"UploadManager",action:"sendMetaUpdate",uploadId:e.id})}}queueDependentMetaUpdate(e){const t={endpoint:"uploads/meta",method:"POST",dependencies:[e.operationId],data:()=>{const t=new FormData;return t.append("operation_id",e.operationId),t.append("upload_id",e.id),t.append("title",e.meta.title),t.append("alt_text",e.meta.alt_text),t.append("caption",e.meta.caption),t.append("description",e.meta.description),t},title:"Updating metadata after upload",canMerge:!0,headers:{action_nonce:jvbSettings.dash}};this.queue.addToQueue(t)}async processFiles(e,t){const a=this.fields.get(e);if(!a)return;const s=t.filter((e=>this.validateFile(e,a)));if(0===s.length)return;if(!this.checkFieldLimits(e,s.length))return;const i=await this.processBatch(e,s);this.maybeLockUploads(e),a.groupDisplay&&(a.groupDisplay.hidden=!1),i.length>0&&await this.queueUpload(e),this.hideUploadProgress(e),this.a11y.announce(`Processed ${i.length} of ${s.length} files`)}checkFieldLimits(e,t){const a=this.fields.get(e);if(!a)return!1;const s=a.uploads?.size||0;return!(s+t>a.maxFiles)||(this.notifications.add(`Cannot add ${t} files. Max ${a.maxFiles} allowed, currently have ${s}.`,"warning"),!1)}generateUploadId(){return`upload_${Date.now()}_${Math.random().toString(36).substr(2,9)}`}validateFile(e,t){return this.settings.allowedTypes.includes(e.type)?!(e.size>this.settings.maxFileSize)||(this.notify(`File too large: ${this.formatBytes(e.size)}`,"error"),!1):(this.notify(`Invalid file type: ${e.type}`,"error"),!1)}formatBytes(e,t=2){if(0===e)return"0 Bytes";const a=t<0?0:t,s=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,s)).toFixed(a))+" "+["Bytes","KB","MB","GB"][s]}async processBatch(e,t){const a=[],s=[],i=this.worker.settings.maxConcurrent;let r=t.length;for(let o=0;o<t.length;o++){this.updateUploadProgress(e,o,r),s.length>=i&&await Promise.race(s);const n=this.processFile(e,t[o]).then((e=>{const t=s.indexOf(n);return t>-1&&s.splice(t,1),e&&a.push(e),e})).catch((e=>{console.error(`Failed to process ${t[o].name}:`,e);const a=s.indexOf(n);return a>-1&&s.splice(a,1),null}));s.push(n)}return await Promise.all(s),a}async processFile(e,t){const a=this.fields.get(e),s=await this.setUpload(e,t),i=s.id;try{this.addImageToGroup(i),this.updateUploadStatus(i,"local_processing");let e=null,r=!1;try{e=await this.processImage(t,i)}catch(a){console.warn(`Processing failed for ${t.name}, using original:`,a),r=!0,e=t}s.processedFile=e,s.processingFailed=r,this.updateUploadStatus(i,"processed"),this.uploads.set(i,s),a&&a.key&&await this.persistFieldState(a.key);const o=r?`${t.name} added (original format)`:`${t.name} processed and ready`;return this.a11y.announce(o),s}catch(e){return this.cleanupFailedUpload(i,a.key),this.error.log(e,{component:"UploadManager",action:"processFile",uploadId:i,fileName:t.name}),null}}async processImage(e,t){const a=this.worker.settings.timeout;return new Promise(((s,i)=>{let r,o=!1;r=setTimeout((()=>{o||(o=!0,this.worker.tasks.delete(t),this.worker.settings.restartAfterTimeout&&this.restartCompressionWorker(),i(new Error(`Processing timeout for ${e.name}`)))}),a),this.worker.tasks.set(t,{file:e,timeoutId:r}),this.handleProcess(e,t).then((e=>{o||(o=!0,clearTimeout(r),this.worker.tasks.delete(t),s(e))})).catch((e=>{o||(o=!0,clearTimeout(r),this.worker.tasks.delete(t),i(e))}))}))}async handleProcess(e,t){if(!e.type.startsWith("image/"))return e;const a=this.getMaxDimension();if(this.shouldUseWorker(e))try{if(this.worker.worker||this.initCompressionWorker(),this.worker.worker)return await this.processWithWorker(e,t,a,.85)}catch(e){console.warn("Worker processing failed, falling back to main thread:",e)}return await this.processOnMainThread(e,a,.85)}async processOnMainThread(e,t,a){return new Promise(((s,i)=>{const r=new Image,o=document.createElement("canvas"),n=o.getContext("2d");let l=null;const d=()=>{r.onload=null,r.onerror=null,l&&(URL.revokeObjectURL(l),l=null),o.width=1,o.height=1,n.clearRect(0,0,1,1)};r.onload=()=>{try{const{width:l,height:c}=this.calculateOptimalDimensions(r,t);o.width=l,o.height=c,n.imageSmoothingEnabled=!0,n.imageSmoothingQuality="high",n.drawImage(r,0,0,l,c);const u=this.getOptimalFormat(e),p=this.getOptimalQuality(e,a);o.toBlob((t=>{if(d(),t){const a=new File([t],this.getProcessedFileName(e,u),{type:u,lastModified:Date.now()});s(a)}else i(new Error("Canvas toBlob failed"))}),u,p)}catch(e){d(),i(new Error(`Canvas processing failed: ${e.message}`))}},r.onerror=()=>{d(),i(new Error(`Failed to load image: ${e.name}`))};try{l=URL.createObjectURL(e),r.src=l}catch(e){d(),i(new Error(`Failed to create object URL: ${e.message}`))}}))}getOptimalFormat(e){return"image/gif"===e.type||"image/svg+xml"===e.type?e.type:this.supportsWebP()?"image/webp":"image/jpeg"}getOptimalQuality(e,t){return e.size<512e3?Math.max(t,.9):e.size<2097152?t:Math.min(t,.8)}getProcessedFileName(e,t){return e.name.replace(/\.[^/.]+$/,"")+({"image/webp":".webp","image/jpeg":".jpg","image/png":".png","image/gif":".gif"}[t]||".jpg")}getMaxDimension(){const e=window.screen.width,t=window.devicePixelRatio||1;return e*t>2560?2400:e*t>1920?1920:1200}shouldUseWorker(e){return this.worker.worker&&e.size>1048576&&"undefined"!=typeof OffscreenCanvas}async processWithWorker(e,t,a,s){return new Promise(((i,r)=>{if(!this.worker.worker)return void r(new Error("Worker not available"));const o=`${t}_${Date.now()}`,n=t=>{if(t.data.messageId===o)if(this.worker.worker.removeEventListener("message",n),this.worker.worker.removeEventListener("error",l),t.data.success){const a=new File([t.data.blob],this.getProcessedFileName(e,t.data.format||"image/webp"),{type:t.data.format||"image/webp",lastModified:Date.now()});i(a)}else r(new Error(t.data.error||"Worker processing failed"))},l=e=>{this.worker.worker.removeEventListener("message",n),this.worker.worker.removeEventListener("error",l),r(new Error(`Worker error: ${e.message}`))};this.worker.worker.addEventListener("message",n),this.worker.worker.addEventListener("error",l),this.worker.worker.postMessage({messageId:o,file:e,maxDimension:a,quality:s,outputFormat:this.getOptimalFormat(e)})}))}restartCompressionWorker(){console.log("Restarting compression worker..."),this.worker.worker&&(this.worker.worker.terminate(),this.worker.worker=null),this.worker.tasks.clear(),this.worker.restart.count>=this.worker.restart.max?console.error("Max worker restarts reached, disabling worker"):(this.worker.restart.count++,this.initCompressionWorker())}initCompressionWorker(){if(!this.worker.worker&&"undefined"!=typeof Worker)try{const e=new Blob(["\n            self.onmessage = async function(e) {\n                const { messageId, file, maxDimension, quality, outputFormat } = e.data;\n\n                try {\n                    // Create ImageBitmap from file\n                    const bitmap = await createImageBitmap(file);\n\n                    // Calculate dimensions\n                    const scale = Math.min(maxDimension / bitmap.width, maxDimension / bitmap.height, 1);\n                    const width = Math.round(bitmap.width * scale);\n                    const height = Math.round(bitmap.height * scale);\n\n                    // Create OffscreenCanvas\n                    const canvas = new OffscreenCanvas(width, height);\n                    const ctx = canvas.getContext('2d');\n\n                    // Draw and resize\n                    ctx.imageSmoothingEnabled = true;\n                    ctx.imageSmoothingQuality = 'high';\n                    ctx.drawImage(bitmap, 0, 0, width, height);\n\n                    // Clean up bitmap\n                    bitmap.close();\n\n                    // Convert to blob\n                    const blob = await canvas.convertToBlob({\n                        type: outputFormat,\n                        quality: quality\n                    });\n\n                    self.postMessage({\n                        messageId,\n                        success: true,\n                        blob: blob,\n                        format: outputFormat\n                    });\n\n                } catch (error) {\n                    self.postMessage({\n                        messageId,\n                        success: false,\n                        error: error.message\n                    });\n                }\n            };\n        "],{type:"application/javascript"});this.worker.worker=new Worker(URL.createObjectURL(e))}catch(e){console.warn("Failed to initialize compression worker:",e),this.worker.worker=null}}calculateOptimalDimensions(e,t){let{width:a,height:s}=e;if(a<=t&&s<=t)return{width:a,height:s};const i=Math.min(t/a,t/s);return{width:Math.round(a*i),height:Math.round(s*i)}}supportsWebP(){return 0===document.createElement("canvas").toDataURL("image/webp").indexOf("data:image/webp")}cleanupFailedUpload(e,t){const a=this.fields.get(t);a?.uploads&&a.uploads.delete(e);const s=this.uploads.get(e);s&&(s.preview?.startsWith("blob:")&&URL.revokeObjectURL(s.preview),s.element?.remove(),this.uploads.delete(e)),this.worker.tasks.delete(e)}updateUploadStatus(e,t){console.log("Updating upload status for: ",e);let a=this.uploads.get(e);a&&(a.status=t,this.updateImageUI(a.id),this.persistFieldState(a.fieldId))}updateImageUI(e){console.log("Updating image UI: ",e);const t=this.uploads.get(e);if(console.log(t),!t?.element)return;const a=t.element.querySelector(".progress"),s=t.element;if(console.log("Updating Upload UI:",t),s&&(s.className=s.className.replace(/status-[\w-]+/g,""),s.classList.add(`status-${t.status}`)),a){let e=this.getStatusIcon(t.status),s=this.getStatusText(t.status),i=this.getStatusProgress(t.status);const r=a.querySelector(".fill"),o=a.querySelector("span.icon"),n=a.querySelector("span.details");r&&(r.style.width=`${i}%`),n&&(n.textContent=s),o&&(window.removeChildren(o),o.append(e)),"completed"===t.status&&setTimeout((()=>{a&&window.fade(a,!1)}),1e3)}}maybeLockUploads(e){const t=this.fields.get(e);t&&t.ui.field.dropZone&&(t.ui.field.dropZone.hidden=t.uploads&&t.uploads.size>=t.maxFiles)}createImageElement(e,t=!1){let a=window.getTemplate("uploadItem");if(!a)return void console.error("Image template not found");a.dataset.uploadId=e.id,a.querySelector('[name="featured"]').value=e.id;let[s,i,r]=[a.querySelector('[name="featured"]'),a.querySelector("img"),a.querySelector("details")];if([s.value,i.src,i.alt]=[e.id,e.preview,e.originalFile?.name??e.meta?.originalName??""],r){let e=window.getTemplate("uploadMeta");e&&r.append(e)}return a.draggable=t,a.querySelectorAll("input").forEach((t=>{let a=t.id;if(a){let s=a+e.id,i=t.parentNode.querySelector(`label[for="${a}"]`);t.id=s,i&&(i.htmlFor=s)}})),a}updateUploadProgress(e,t,a,s){const i=this.fields.get(e);if(!i)return;let r=i.ui.field.progress.progress;if(!r){r=window.getTemplate("imageProgress");const e=i.dropZone||i.container.firstElementChild;e?e.insertAdjacentElement("afterend",r):i.container.prepend(r)}const o=a>0?Math.round(t/a*100):0,n=i.ui.field.progress.fill,l=i.ui.field.progress.details,d=i.ui.field.progress.count;n&&(n.style.width=`${o}%`),l&&(l.textContent=s),d&&(d.textContent=`${t}/${a}`),t===a&&r.classList.add("completed")}hideUploadProgress(e){const t=this.fields.get(e);if(!t)return;const a=t.ui.field.progress.progress;a&&window.fade(a,!1)}async initDB(){if(!("indexedDB"in window))return;const e=indexedDB.open("jvb_uploads_db",1);e.onupgradeneeded=e=>{const t=e.target.result;if(!t.objectStoreNames.contains("fieldStates")){const e=t.createObjectStore("fieldStates",{keyPath:"fieldId"});e.createIndex("timestamp","timestamp",{unique:!1}),e.createIndex("content","content",{unique:!1}),e.createIndex("itemId","itemId",{unique:!1})}t.objectStoreNames.contains("uploadBlobs")||t.createObjectStore("uploadBlobs",{keyPath:"uploadId"})},e.onsuccess=e=>{this.db=e.target.result,this.loadFields()},e.onerror=e=>{console.error("IndexedDB error:",e)}}async loadFields(){if(this.db)return new Promise((e=>{const t=this.db.transaction(["fieldStates","uploadBlobs"],"readonly"),a=t.objectStore("fieldStates"),s=t.objectStore("uploadBlobs");a.getAll().onsuccess=t=>{t.target.result.forEach((e=>{let t=e.uploads,a=t.map((e=>e.id));e.uploads=new Set(a),this.fields.set(e.key,e),t.forEach((e=>{this.uploads.set(e.id,e)}))})),this.notify("uploads-loaded",{items:Array.from(this.uploads.values())}),e()};s.getAll().onsuccess=t=>{t.target.result.forEach((e=>{this.uploadBlobs.set(e.id,e)})),this.notify("blobs-loaded",{items:Array.from(this.uploadBlobs.values())}),e()}}))}getUpload(e){return this.uploads.get(e)}clearField(e){let t=Array.from(this.fields.uploads);if(t.forEach((e=>{this.uploads.delete(e)})),this.fields.delete(e),this.db){const a=this.db.transaction(["fieldStates","uploadBlobs"],"readwrite");a.objectStore("fieldStates").delete(e),t.forEach((e=>{a.objectStore("uploadBlobs").delete(e)}))}}updateFieldStatus(e,t){const a=this.fields.get(e);if(!a)return;a.uploads.forEach((e=>{console.log("Attempting to set upload to status: ",t),this.updateUploadStatus(e,t)}));const s=a.ui.field.field;if(s){s.dataset.uploadStatus=t;const e=s.querySelector(".submit-uploads");e&&(e.disabled="uploading"===t||"processing"===t)}}handleUploadComplete(e){const t=e.response;if(!t?.uploads)return;t.uploads.forEach((e=>{const t=this.uploads.get(e.upload_id);t&&(t.attachmentId=e.attachment_id,this.updateUploadStatus(e.upload_id,"completed"),this.uploads.set(t.id,t),this.clearUpload(t.id))}));const a=e.data.get("field_key");a&&this.persistFieldState(a)}async clearUpload(e){const t=this.uploads.get(e);if(t&&(t.preview?.startsWith("blob:")&&URL.revokeObjectURL(t.preview),this.persistFieldState(t.fieldId),this.uploads.delete(e),this.uploadBlobs.delete(e),this.db)){const t=this.db.transaction(["uploadBlobs"],"readwrite");await t.objectStore("uploadBlobs").delete(e)}}async setUpload(e,t,a=null){a||(a=this.generateUploadId());const s={id:a,fieldId:e,groupId:null,originalFile:t,processedFile:null,status:"received",progress:{percent:0,message:"Received..."},preview:URL.createObjectURL(t),createdAt:Date.now(),meta:{title:"",alt_text:"",caption:"",originalName:t.name,originalType:t.type,originalSize:t.size},changes:{}},i=this.fields.get(e);return i?(i.uploads||(i.uploads=new Set),i.uploads.add(a),s.element=this.createImageElement(s,"groupable"===i.type),s.ui=window.uiFromSelectors(this.selectors.item,s.element),this.uploads.set(a,s),this.updateImageUI(a),await this.persistFieldState(e),s):(console.error(`Field ${e} not found`),null)}getFieldUploads(e,t){const a=this.fields.get(e);return console.log("Got field uploads: ",a),a?.uploads?Array.from(a.uploads).map((e=>{let a=this.uploads.get(e);if(!a)return null;if(t){const{element:e,ui:t,...s}=a;a=s}return a})).filter(Boolean):[]}async persistFieldState(e){if(!this.db)return;const t=this.fields.get(e);if(!t)return;const{ui:a,container:s,dropZone:i,previewGrid:r,selectAll:o,selectActions:n,selectInfo:l,selectCount:d,groupDisplay:c,...u}=t,p={fieldId:e,timestamp:Date.now(),config:{key:u.key,id:u.id,name:u.name,type:u.type,content:u.content,itemID:u.itemID,context:u.context,mode:u.mode,maxFiles:u.maxFiles,multiple:u.multiple},context:{url:window.location.href,modalType:this.getModalType(t),formId:t.formId},uploads:this.getFieldUploads(e,!0),groups:Array.from(this.groups.entries()).filter((([t,a])=>a.fieldId===e&&a.uploads.size>0)).map((([e,t])=>({id:t.id,uploads:Array.from(t.uploads),meta:t.meta,changes:t.changes})))},h=this.db.transaction(["fieldStates"],"readwrite");await h.objectStore("fieldStates").put(p)}async checkPendingUploads(){if(!this.db)return;const e=this.db.transaction(["fieldStates"],"readonly").objectStore("fieldStates"),t=(await new Promise((t=>{const a=e.getAll();a.onsuccess=()=>t(a.result)}))).filter((e=>e.uploads.some((e=>"processing"===e.status||"processed"===e.status||"pending"===e.status))));0!==t.length&&this.showRecoveryNotification(t)}showRecoveryNotification(e){const t=e.reduce(((e,t)=>e+t.uploads.length),0);let a=window.getTemplate("restoreNotification");[a.querySelector(".restore-details").textContent]=[`${t} upload(s) from ${e.length} field(s) can be recovered.`],e.forEach((e=>{console.log(e);let t=window.getTemplate("restoreField");e.uploads.forEach((e=>{let a=window.getTemplate("restoreItem");[a.querySelector("img").src]=[e.preview],t.append(a)})),a.append(t)})),a.querySelector('[data-action="restore"]').addEventListener("click",(()=>{this.restoreFieldStates(e),a.remove()})),a.querySelector('[data-action="dismiss"]').addEventListener("click",(()=>{this.notifications.add("Uploads saved for later restoration","info"),a.remove()})),a.querySelector('[data-action="clear"]').addEventListener("click",(()=>{this.clearCachedUploads(e),a.remove()})),document.body.appendChild(a)}async restoreFieldStates(e){const t=new Map;if(e.forEach((e=>{t.has(e.context.url)||t.set(e.context.url,[]),t.get(e.context.url).push(e)})),1===t.size&&t.has(window.location.href)){for(const t of e)await this.restoreField(t);this.notifications.add(`Restored ${e.length} field(s)`,"success")}else{sessionStorage.setItem("jvb_restore_uploads",JSON.stringify(e));const a=t.keys().next().value;window.location.href!==a&&(window.location.href=a)}}async restoreField(e){const{config:t,context:a,uploads:s,groups:i}=e;a.modalType&&await this.openModalForRestore(a);const r=document.querySelector(`.field.image[data-field-id="${t.id}"]`);if(!r)return void console.warn(`Field ${t.id} not found for restoration`);const o=this.registerUploader(r,t),n=this.fields.get(o);for(const e of s)await this.restoreUpload(n,e);i&&i.length>0&&await this.restoreGroups(n,i,s),this.maybeLockUploads(o),"direct"===t.mode&&await this.queueUpload(o)}async restoreUpload(e,t){const a=await this.getBlobData(t.id);let s=null;a&&(s=new File([a.data],a.name,{type:a.type,lastModified:a.lastModified}),t.processedFile=s),e.uploads||(e.uploads=new Set),e.uploads.add(t.id),t.element=this.createImageElement(t,"groupable"===e.type);const i=t.groupId?e.ui.groups.groups.get(t.groupId):e.ui.field.preview;i&&(i.append(t.element),t.location=i),this.uploads.set(t.id,t)}async restoreGroups(e,t,a){for(const s of t){const t=this.createGroupElement(s.id,e.key);e.ui.groups.groups.set(s.id,t),e.ui.groups.container.insertBefore(t,e.ui.groups.empty);const i=new Set(s.uploadIds);this.groups.set(s.id,i),s.meta&&this.groupsMeta.set(s.id,s.meta),s.uploadIds.forEach((e=>{const i=a.find((t=>t.id===e));i&&i.element&&(t.querySelector(".item-grid").append(i.element),i.location=t.querySelector(".item-grid"),i.groupId=s.id)}))}}async getBlobData(e){if(!this.db)return null;const t=this.db.transaction(["uploadBlobs"],"readonly").objectStore("uploadBlobs").get(e);return new Promise((e=>{t.onsuccess=()=>e(t.result),t.onerror=()=>e(null)}))}async openModalForRestore(e){const{modalType:t,formId:a}=e;let s=null;switch(t){case"create":s=document.querySelector('[data-action="create"]');break;case"edit":s=document.querySelector(`[data-action="edit"][data-id="${e.itemId}"]`);break;case"bulkEdit":s=document.querySelector('[data-action="bulk-edit"]')}s&&(s.click(),await new Promise((e=>setTimeout(e,300))))}async clearCachedUploads(e){if(!this.db)return;const t=this.db.transaction(["fieldStates","uploadBlobs"],"readwrite");for(const a of e){await t.objectStore("fieldStates").delete(a.fieldId);for(const e of a.uploads)await t.objectStore("uploadBlobs").delete(e.id),e.preview?.startsWith("blob:")&&URL.revokeObjectURL(e.preview)}this.notifications.add("Cached uploads cleared","info")}async checkRestorationIntent(){const e=sessionStorage.getItem("jvb_restore_uploads");if(!e)return;const t=JSON.parse(e),a=t.filter((e=>e.context.url===window.location.href));if(a.length>0){for(const e of a)await this.restoreField(e);const e=t.filter((e=>e.context.url!==window.location.href));e.length>0?sessionStorage.setItem("jvb_restore_uploads",JSON.stringify(e)):sessionStorage.removeItem("jvb_restore_uploads"),this.notifications.add(`Restored ${a.length} field(s)`,"success")}}addImageToGroup(e,t=null,a=!0){let s=this.getUpload(e);if(!s)return;let i=this.fields.get(s.fieldId);if(i&&(t||s.location!==i.ui.field.preview)&&t!==s.location){if(s.location){let t=s.location.dataset.groupId;if(t){let a=this.groups.get(t);a&&(a.delete(e),0===a.size&&this.removeGroup(t))}}if(s.element.querySelector('[name="featured"]').hidden=!t,t){let a=t.dataset.groupId,i=this.groups.get(a);i||(i=this.createGroup(s.fieldId)),i.uploads.add(e)}else t=i.ui.field.preview;t.append(s.element),a&&this.persistFieldState(i.key)}}addSelectionToGroup(e){let t=this.getFieldFromElement(e);if(!t)return;if(0===this.selected.get(t.key).size)return;let a=this.getGroupFromElement(e);a||(a=this.createGroup(t.key)),Array.from(this.selected).forEach((e=>{this.addImageToGroup(e,a.grid,!1)})),this.persistFieldState(a.fieldId)}removeGroup(e,t=!1){let a=this.groups.get(e);if(!a)return;if(t&&!window.confirm("This will delete this group. Any uploads in this group will return to the main grid. Are you sure?"))return;a.uploads.size>0&&Array.from(a.uploads).forEach((e=>{this.addImageToGroup(e)}));let s=a.element;s&&(window.fade(s,!1),this.a11y.announce("Empty group removed")),this.persistFieldState(a.fieldId)}createGroup(e){let t=this.fields.get(e);if(!t)return;let a=t.ui.groups.size;t.ui.groups.groups.set(`group-${a}`,this.createGroupElement(`group-${a}`,e));let s=t.ui.groups.groups.get(`group-${a}`);t.ui.groups.container.insertAfter(s,t.ui.groups.empty);let i={fieldId:t.key,id:`group-${a}`,element:s,grid:s.querySelector(".item-grid"),uploads:new Set,meta:{post_title:"",post_excerpt:""},changes:{}};return this.groups.set(`group-${a}`,i),i}createGroupElement(e,t){let a=window.getTemplate("imageGroup");if(!a)return;a.dataset.groupId=e,a.dataset.fieldId=t;let s=window.getTemplate("groupMetaData");return a.querySelector(".fields")?.append(s),a}handleSelectAll(e,t=null){const a=this.getFieldFromElement(e);if(!a)return;null===t&&(t=e.checked);(a.previewGrid.querySelectorAll("[data-upload-id]")||[]).forEach((e=>{const a=e.querySelector('[name*="select-item"]');a&&(a.checked=t)})),this.updateSelectAll(e),this.a11y.announce(t?"All uploads selected":"All uploads deselected"),this.lastClickedUpload=null}updateSelection(e){let t=this.getFieldFromElement(e.target),a=this.getUploadFromElement(e.target);t&&a?(this.lastClickedUpload=a.id,e.target.checked?this.selected.get(t.key).add(a.id):this.selected.get(t.key).delete(a.id)):console.log("No field or upload found...")}updateSelectAll(e){const t=this.getFieldFromElement(e);if(!t)return;const a=this.getSelectedUploads(e);a.length>0?(t.selectActions.hidden=!1,t.selectInfo.hidden=!1,t.selectCount.textContent=`${a.length}`):(t.selectActions.hidden=!0,t.selectInfo.hidden=!0);let s=a.length===t.container.querySelectorAll(".item-grid.preview .upload-item").length;t.selectAll.checked=s,t.selectAll.nextElementSibling.textContent=s?"Clear Selection":"Select All"}getSelectedUploads(e){let t=this.getFieldFromElement(e);if(t)return Array.from(this.selected.get(t.key)??[])}handleRangeSelection(e,t){if(!this.getFieldFromElement(e))return;const a=this.getUploadIdFromElement(e);if(!a||!this.lastClickedUpload)return;const s=e.closest(".item-grid"),i=Array.from(s.querySelectorAll("[data-upload-id]")),r=i.findIndex((e=>e.dataset.uploadId===this.lastClickedUpload)),o=i.findIndex((e=>e.dataset.uploadId===a));if(-1===r||-1===o)return;const n=Math.min(r,o),l=Math.max(r,o);for(let e=n;e<=l;e++){const t=i[e].querySelector('[name*="select-item"]');t&&(t.checked=!0)}e.checked=!0,this.updateSelectAll(e);const d=l-n+1;this.a11y.announce(`Selected ${d} items in range`),this.lastClickedUpload=a}removeSelection(e){let t=this.getFieldIdFromElement(e);const a=this.getSelectedUploads(e);0!==a.length?a.forEach((e=>{this.removeUpload(t,e)})):this.notify("No uploads selected","warning")}removeUpload(e,t){const a=this.fields.get(e),s=this.uploads.get(t);if(a&&s){if(a.uploads?.delete(t),s.groupId){const e=this.groups.get(s.groupId);e?.delete(t)}s.element?.remove(),this.clearUpload(t),this.maybeLockUploads(e),this.updateSelectAll(a.ui.field.field),this.a11y.announce("Upload removed")}}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((a=>a(e,t)))}handleBeforeUnload(e){if(Array.from(this.uploads.values()).filter((e=>"processing"===e.status||"pending"===e.status||"uploading"===e.status)).length>0){const t="You have uploads in progress. Are you sure you want to leave?";return e.preventDefault(),e.returnValue=t,t}}cleanup(){this.clearListeners(),this.hasGroups&&this.clearGroupListeners(),this.compressionWorker=null,this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",(()=>{window.jvbUploads=new e}))})();