| | |
| | | (()=>{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}))})(); |
| | | (()=>{class e{constructor(){this.queue=window.jvbQueue,this.a11y=window.jvbA11y,this.error=window.jvbError,this.fieldStore=new window.jvbStore({name:"upload_fields",storeName:"fieldStates",keyPath:"id",version:2,indexes:[{name:"fieldId",keyPath:"fieldId"},{name:"timestamp",keyPath:"timestamp"},{name:"content",keyPath:"content"},{name:"itemId",keyPath:"itemId"},{name:"status",keyPath:"status"}],stripDOMReferences:!0,TTL:6048e5}),this.uploadStore=new window.jvbStore({name:"uploads",storeName:"uploads",keyPath:"id",storeBlobs:!0,indexes:[{name:"fieldId",keyPath:"fieldId"},{name:"status",keyPath:"status"},{name:"groupId",keyPath:"groupId"},{name:"attachmentId",keyPath:"attachmentId"}]}),this.fieldStore.subscribe(this.handleFieldStoreEvent.bind(this)),this.uploadStore.subscribe(this.handleUploadStoreEvent.bind(this)),this.initWorker(),this.fields=new Map,this.uploads=new Map,this.uploadBlobs=new Map,this.groups=new Map,this.selected=new Map,this.selectionHandlers=new Map,this.previewUrls=new Set,this.subscribers=new Set,this.dragController=null,this.selectors={field:{field:"[data-upload-field]",input:'input[type="file"]',hiddenValue:'input[type="hidden"]',dropZone:".file-upload-container",preview:".item-grid.preview",progress:".image-progress"},groups:{container:".upload-group",grid:".item-grid.group",header:".group-header",selectAll:'[name="select-all-group"]',actions:".group-actions",count:".selection-controls .info"},items:{item:"[data-upload-id]",checkbox:'[name*="select-item"]',featured:'[name="featured"]',details:"details"}},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(){await this.loadFields(),await this.loadUploads(),this.initializeFields(),this.initListeners(),this.queue.subscribe(((e,t)=>{if("uploads"!==t.endpoint&&"uploads/meta"!==t.endpoint)return;const s=t.data instanceof FormData?t.data.get("fieldId"):t.data.fieldId;switch(e){case"cancel-operation":s&&this.clearField(s);break;case"operation-status":s&&this.updateFieldStatus(s,t.status);break;case"operation-complete":(t.result?.data||[]).forEach((e=>{const t=this.uploads.get(e.upload_id);t&&(t.attachmentId=e.attachment_id,t.status="completed",this.uploads.set(t.id,t))})),s&&this.cleanField(s)}})),window.addEventListener("beforeunload",(()=>{this.cleanupAllPreviewUrls()}))}initWorker(){this.worker={worker:null,timeout:null,tasks:new Map,restart:{count:0,max:3},settings:{timeout:1e4,batchSize:1,maxConcurrent:3,restartAfterTimeout:!0}}}initializeFields(){document.querySelectorAll(this.selectors.field.field).forEach((e=>{this.registerUploader(e)}))}scanFields(e){e.querySelectorAll(this.selectors.field.field).forEach((e=>{this.registerUploader(e)}))}registerUploader(e){const t=this.determineFieldId(e),s=this.extractFieldConfig(e),r={id:t,config:s,element:e,ui:this.buildFieldUI(e),uploads:new Set,groups:new Set,state:"ready"};return this.fields.set(t,r),e.dataset.uploader=t,this.addFieldSelectionHandler(t),"post_group"!==s.destination||this.dragController||this.initGroupFeatures(),t}extractFieldConfig(e){return{destination:e.dataset.destination||"meta",content:e.dataset.content||null,mode:e.dataset.mode||"direct",type:e.dataset.type||"single",name:e.dataset.field,itemID:e.dataset.itemId||0,maxFiles:parseInt(e.dataset.maxFiles)||999,subtype:e.dataset.subtype||"image"}}buildFieldUI(e){let t={field:e,input:e.querySelector(this.selectors.field.input),dropZone:e.querySelector(this.selectors.field.dropZone),preview:e.querySelector(this.selectors.field.preview),progress:{progress:e.querySelector(this.selectors.field.progress),bar:e.querySelector(".bar"),fill:e.querySelector(".fill"),details:e.querySelector(".details"),text:e.querySelector(".details .text"),count:e.querySelector(".details .count")}},s=e.querySelector(".group-display");return s&&(t.groups={display:s,container:e.querySelector(".item-grid.groups"),empty:e.querySelector(".empty-group"),groups:new Map}),t}initListeners(){this.clickHandler=this.handleClick.bind(this),this.changeHandler=this.handleChange.bind(this),document.addEventListener("click",this.clickHandler),document.addEventListener("change",this.changeHandler),this.dragEnterHandler=this.handleExternalDragEnter.bind(this),this.dragLeaveHandler=this.handleExternalDragLeave.bind(this),this.dragOverHandler=this.handleExternalDragOver.bind(this),this.dropHandler=this.handleExternalDrop.bind(this),document.addEventListener("dragenter",this.dragEnterHandler),document.addEventListener("dragleave",this.dragLeaveHandler),document.addEventListener("dragover",this.dragOverHandler),document.addEventListener("drop",this.dropHandler)}initGroupFeatures(){this.dragController=new window.jvbDragHandler({draggableSelector:this.selectors.items.item,dropTargetSelector:`${this.selectors.field.preview}, ${this.selectors.groups.grid}, .empty-group`,ignoreSelector:"input:not(.upload-select), button, select, textarea, details, summary, a",previewElement:"img, video, .icon",getItemId:e=>e.dataset.uploadId,getSelectedItems:e=>{const t=this.getFieldIdFromElement(e),s=e.dataset.uploadId,r=this.getCurrentSelection(t);return r&&r.includes(s)?r:[s]},validateDrop:(e,t)=>{const s=this.getFieldIdFromElement(t),r=document.querySelector(`[data-upload-id="${e[0]}"]`);return s===this.getFieldIdFromElement(r)},onDrop:(e,t)=>{this.handleItemDrop(e,t),t.scrollIntoView({behavior:"smooth",block:"center"})},onDragStart:e=>{},onDragEnd:(e,t)=>{if(t){const t=document.querySelector(`[data-upload-id="${e[0]}"]`),s=this.getFieldIdFromElement(t),r=this.selectionHandlers.get(s);r?.clearSelection()}},previewOptions:{multiOffset:{x:-60,y:-80},singleOffset:{x:-50,y:-60},showCount:!0}})}handleExternalDragLeave(e){const t=e.target.closest(this.selectors.field.dropZone);t&&!t.contains(e.relatedTarget)&&t.classList.remove("dragover")}handleExternalDragEnter(e){if(!e.dataTransfer.types.includes("Files"))return;const t=e.target.closest(this.selectors.field.dropZone);t&&(e.preventDefault(),t.classList.add("dragover"))}handleExternalDragOver(e){if(!e.dataTransfer.types.includes("Files"))return;e.target.closest(this.selectors.field.dropZone)&&(e.preventDefault(),e.dataTransfer.dropEffect="copy")}handleExternalDrop(e){const t=e.target.closest(this.selectors.field.dropZone);if(!t)return;e.preventDefault(),t.classList.remove("dragover");const s=Array.from(e.dataTransfer.files);if(0===s.length)return;const r=this.getFieldIdFromElement(t);r?(this.processFiles(r,s),this.a11y.announce(`${s.length} file(s) dropped for upload`)):console.error("No field ID found for drop zone")}handleItemDrop(e,t){const s=t.classList.contains("preview");let r=t;if(t.classList.contains("empty-group")){const e=this.getFieldIdFromElement(t),s=this.createGroup(e);if(!s)return void console.error("Failed to create group");r=s.grid}e.forEach((e=>{s?this.removeFromGroup(e):this.addToGroup(e,r)}));const o=this.getFieldIdFromElement(t);this.schedulePersistance(o);const i=e.length>1?`Moved ${e.length} items`:"Moved item";this.a11y.announce(i)}handleClick(e){if(e.target.matches(this.selectors.field.dropZone)||e.target.closest(this.selectors.field.dropZone)){const t=e.target.closest(this.selectors.field.dropZone);if(t&&!e.target.matches("input, button, a")){const e=t.querySelector(this.selectors.field.input);e?.click()}}const t=e.target.closest("[data-action]");t&&this.handleAction(t)}handleChange(e){const t=this.getFieldIdFromElement(e.target);if(e.target.matches(this.selectors.field.input)){const t=this.getFieldIdFromElement(e.target),s=Array.from(e.target.files);s.length>0&&t&&this.processFiles(t,s)}t&&("post_group"===this.fields.get(t).config.destination?this.handleGroupMetaChange(e.target):this.queueUploadMeta(e))}getCurrentSelection(e){let t=[];for(let[s,r]of this.selectionHandlers)(e===s||s.includes(e))&&r.selectedItems.size>0&&(t=t.concat([...r.selectedItems]));return t}getSubtypeFromMime(e){return e.startsWith("image/")?"image":e.startsWith("video/")?"video":"document"}getStatusText(e){return this.statusMapping[e]||e}getStatusIcon(e){return window.getIcon(this.queue.icons[e])}getStatusProgress(e){switch(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}}getModalType(e){if(void 0!==e._cachedModalType)return e._cachedModalType;if(!e||!e.element)return e._cachedModalType=null,null;const t=e.element.closest("dialog");if(!t)return e._cachedModalType=null,null;let s=null;return s=t.classList.contains("edit")?"edit":t.classList.contains("create")?"create":t.classList.contains("bulkEdit")?"bulkEdit":t.className,e._cachedModalType=s,s}handleAction(e){const t=e.dataset.action,s=this.getFieldIdFromElement(e);switch(t){case"add-to-group":this.handleAddToGroup(e);break;case"delete-group":this.handleDeleteGroup(e);break;case"delete-upload":case"remove-from-group":this.handleRemoveItem(e);break;case"upload":this.fields.get(s).element.closest("details").open=!1,document.body.classList.add("uploading"),this.submitUploads(s);break;case"restore":this.handleRestoreUploads().then((()=>{}));break;case"clear-cache":confirm("Save these uploads for later?")||this.cleanupStoredUploads(),this.cleanupRestore()}}handleAddToGroup(e){const t=e.closest(this.selectors.field.field),s=t?.dataset.uploader;if(!s)return;const r=this.selected.get(s);if(r&&0!==r.size){const e=this.createGroup(s);if(!e)return;r.forEach((t=>{this.addToGroup(t,e.grid)}));const t=this.selectionHandlers.get(s);t?.clearSelection(),this.a11y.announce(`Created group with ${r.size} items`)}else this.createGroup(s);this.schedulePersistance(s)}handleDeleteGroup(e){const t=e.closest(this.selectors.groups.container);if(!t)return;const s=t.dataset.groupId,r=this.getFieldIdFromElement(t);if(!confirm("Delete this group? Items will be moved back to the upload area."))return;t.querySelectorAll(this.selectors.items.item).forEach((e=>{const t=e.dataset.uploadId;this.removeFromGroup(t)})),this.deleteGroup(s),this.a11y.announce("Group deleted, items returned to upload area"),this.schedulePersistance(r)}handleRemoveItem(e){const t=e.closest(this.selectors.items.item);if(!t)return;const s=t.dataset.uploadId,r=this.getFieldIdFromElement(t);confirm("Remove this item?")&&(this.removeUpload(r,s),this.a11y.announce("Item removed"),this.schedulePersistance(r))}addFieldSelectionHandler(e){if(this.selectionHandlers.has(e))return this.selectionHandlers.get(e);const t=this.fields.get(e);if(!t)return;const s=t.ui.field;if(!s)return;const r=new window.jvbHandleSelection({container:s,ui:{selectAll:s.querySelector('[name="select-all-uploads"]'),bulkControls:s.querySelector(".selection-actions"),count:s.querySelector(".selection-count")},itemSelector:"[data-upload-id]",checkboxSelector:'[name*="select-item"]'});return r.subscribe(((t,s)=>{switch(t){case"item-selected":case"item-deselected":case"range-selected":this.selected.set(e,s.selectedItems);break;case"select-all":this.handleSelectAll(s.container,s.selected)}})),this.selectionHandlers.set(e,r),r}addGroupSelectionHandler(e,t){const s=`${e}_${t}`;if(this.selectionHandlers.has(s))return this.selectionHandlers.get(s);const r=this.groups.get(t);if(!r)return;const o=new window.jvbHandleSelection({container:r.element,ui:{selectAll:r.element.querySelector(this.selectors.groups.selectAll),bulkControls:r.element.querySelector(this.selectors.groups.actions),count:r.element.querySelector(this.selectors.groups.count)},itemSelector:"[data-upload-id]",checkboxSelector:'[name*="select-item"]'});return o.subscribe(((t,s)=>{switch(t){case"item-selected":case"item-deselected":case"range-selected":this.selected.set(e,s.selectedItems);break;case"select-all":this.handleSelectAll(s.container,s.selected)}})),this.selectionHandlers.set(s,o),o}handleSelectAll(e,t){}determineFieldId(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||""}`}getFromElement(e,t){const s={field:{selector:this.selectors.field.field,key:"uploader",store:this.fields},upload:{selector:this.selectors.items.item,key:"uploadId",store:this.uploads},group:{selector:this.selectors.groups.container,key:"groupId",store:this.groups}}[t];if(!s)return null;const r=e.closest(s.selector);if(!r)return null;const o=r.dataset[s.key];return s.store.get(o)}getFieldFromElement(e){return this.getFromElement(e,"field")}getUploadFromElement(e){return this.getFromElement(e,"upload")}getGroupFromElement(e){return this.getFromElement(e,"group")}getFieldIdFromElement(e){return this.getFromElement(e,"field")?.id??null}getUploadIdFromElement(e){return this.getFromElement(e,"upload")?.id??null}getGroupIdFromElement(e){return this.getFromElement(e,"group")?.id??null}async processFiles(e,t){const s=this.fields.get(e);if(!s)return;s.ui.dropZone&&(s.ui.dropZone.hidden=!0),s.ui.groups.display&&(s.ui.groups.display.hidden=!1);const r=t.length;let o=0;this.updateUploadProgress(e,0,r,"Processing files..."),s.uploads||(s.uploads=new Set);const i=Array.from(t).map((async(t,i)=>{try{const i=`upload_${Date.now()}_${Math.random().toString(36).substr(2,9)}`,a={id:i,attachment_id:null,fieldId:e,originalFile:t,processedFile:null,preview:null,status:"local_processing",element:null,location:null,meta:{originalName:t.name,size:t.size,type:t.type}};a.preview=this.createPreviewUrl(t),t.type.startsWith("image/")?a.processedFile=await this.processImage(t,s.subtype):a.processedFile=t,await this.uploadStore.saveBlob(i,a.processedFile||t);const l=this.getSubtypeFromMime(t.type);return a.element=this.createUploadElement({...a,subtype:l},"post_group"===s.config.destination),this.showUploadProgress(i,!0),this.updateUploadItemProgress(i,50,"local_processing"),s.ui.preview&&(s.ui.preview.appendChild(a.element),a.location=s.ui.preview),this.uploads.set(i,a),s.uploads.add(i),o++,this.updateUploadProgress(e,o,r,"Processing files..."),this.updateUploadItemProgress(i,100,"processed"),a.status="processed",setTimeout((()=>{this.showUploadProgress(i,!1)}),1e3),i}catch(s){return console.error("Error processing file:",t.name,s),o++,this.updateUploadProgress(e,o,r,"Processing files..."),null}}));await Promise.all(i),this.updateFieldState(e),await this.schedulePersistance(e),"post_group"!==s.config.destination&&(await this.queueUpload(e),this.maybeLockUploads(e))}updateFieldState(e){const t=this.fields.get(e);if(!t||!t.ui.field)return;const s=t.ui.field,r=t.uploads?.size||0,o=t.ui.groups?.container?.querySelectorAll(".upload-group").length>0;s.dataset.hasUploads=r>0?"true":"false",s.dataset.uploadCount=r.toString(),s.dataset.hasGroups=o?"true":"false",t.ui.preview&&t.ui.preview.setAttribute("aria-label",`Upload preview area with ${r} item${1!==r?"s":""}`)}updateUploadProgress(e,t,s,r){const o=this.fields.get(e);if(!o?.ui?.progress?.progress)return;const i=o.ui.progress,a=s>0?t/s*100:0;i.fill&&(i.fill.style.width=`${a}%`),i.text&&(i.text.textContent=r),i.count&&(i.count.textContent=`${t}/${s}`),i.progress.hidden=t===s}updateFieldStatus(e,t){const s=this.fields.get(e);s&&(s.state=t)}updateUploadStatus(e,t){const s=this.uploads.get(e);s&&(s.status=t,this.updateUploadUI(e))}updateUploadUI(e){const t=this.uploads.get(e);if(!t?.element)return;t.element.className=t.element.className.replace(/status-[\w-]+/g,""),t.element.classList.add(`status-${t.status}`);t.element.querySelector(".progress")&&this.updateUploadItemProgress(e,this.getStatusProgress(t.status),t.status)}showUploadProgress(e,t=!0){const s=this.uploads.get(e);if(!s||!s.element)return;const r=s.element.querySelector(".progress");r&&(t?(r.style.removeProperty("animation"),r.hidden=!1):(r.style.animation="fadeOut var(--transition-base)",setTimeout((()=>{r.hidden=!0}),300)))}updateUploadItemProgress(e,t,s=null){const r=this.uploads.get(e);if(!r||!r.element)return;const o=r.element.querySelector(".progress");if(!o)return;const i=o.querySelector(".fill"),a=o.querySelector(".details"),l=o.querySelector(".icon");i&&(i.style.width=`${t}%`),s&&a&&(a.textContent=this.getStatusText(s)),s&&l&&(l.innerHTML=this.getStatusIcon(s).outerHTML)}checkFieldLimits(e,t){const s=this.fields.get(e);if(!s)return!1;return(s.uploads?.size||0)+t<=s.maxFiles}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 s=t<0?0:t,r=Math.floor(Math.log(e)/Math.log(1024));return parseFloat((e/Math.pow(1024,r)).toFixed(s))+" "+["Bytes","KB","MB","GB"][r]}shouldProcessClientSide(e,t){return!("image"!==t||!e.type.startsWith("image/"))}async processImage(e,t){const s=this.worker.settings.timeout;return new Promise(((r,o)=>{let i,a=!1;i=setTimeout((()=>{a||(a=!0,this.worker.tasks.delete(t),this.worker.settings.restartAfterTimeout&&this.restartCompressionWorker(),o(new Error(`Processing timeout for ${e.name}`)))}),s),this.worker.tasks.set(t,{file:e,timeoutId:i}),this.handleProcess(e,t).then((e=>{a||(a=!0,clearTimeout(i),this.worker.tasks.delete(t),r(e))})).catch((e=>{a||(a=!0,clearTimeout(i),this.worker.tasks.delete(t),o(e))}))}))}async handleProcess(e,t){if(!e.type.startsWith("image/"))return e;const s=this.getMaxDimension();if(this.shouldUseWorker(e))try{if(this.worker.worker||this.initCompressionWorker(),this.worker.worker)return await this.processWithWorker(e,t,s,.85)}catch(e){console.warn("Worker processing failed, falling back to main thread:",e)}return await this.processOnMainThread(e,s,.85)}async processOnMainThread(e,t,s){return new Promise(((r,o)=>{const i=new Image,a=document.createElement("canvas"),l=a.getContext("2d");let n=null;const d=()=>{i.onload=null,i.onerror=null,n&&(URL.revokeObjectURL(n),n=null),a.width=1,a.height=1,l.clearRect(0,0,1,1)};i.onload=()=>{try{const{width:n,height:c}=this.calculateOptimalDimensions(i,t);a.width=n,a.height=c,l.imageSmoothingEnabled=!0,l.imageSmoothingQuality="high",l.drawImage(i,0,0,n,c);const u=this.getOptimalFormat(e),p=this.getOptimalQuality(e,s);a.toBlob((t=>{if(d(),t){const s=new File([t],this.getProcessedFileName(e,u),{type:u,lastModified:Date.now()});r(s)}else o(new Error("Canvas toBlob failed"))}),u,p)}catch(e){d(),o(new Error(`Canvas processing failed: ${e.message}`))}},i.onerror=()=>{d(),o(new Error(`Failed to load image: ${e.name}`))};try{n=this.createPreviewUrl(e),i.src=n}catch(e){d(),o(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,s,r){return new Promise(((o,i)=>{if(!this.worker.worker)return void i(new Error("Worker not available"));const a=`${t}_${Date.now()}`,l=t=>{if(t.data.messageId===a)if(this.worker.worker.removeEventListener("message",l),this.worker.worker.removeEventListener("error",n),t.data.success){const s=new File([t.data.blob],this.getProcessedFileName(e,t.data.format||"image/webp"),{type:t.data.format||"image/webp",lastModified:Date.now()});o(s)}else i(new Error(t.data.error||"Worker processing failed"))},n=e=>{this.worker.worker.removeEventListener("message",l),this.worker.worker.removeEventListener("error",n),i(new Error(`Worker error: ${e.message}`))};this.worker.worker.addEventListener("message",l),this.worker.worker.addEventListener("error",n),this.worker.worker.postMessage({messageId:a,file:e,maxDimension:s,quality:r,outputFormat:this.getOptimalFormat(e)})}))}restartCompressionWorker(){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(this.createPreviewUrl(e))}catch(e){console.warn("Failed to initialize compression worker:",e),this.worker.worker=null}}calculateOptimalDimensions(e,t){let{width:s,height:r}=e;if(s<=t&&r<=t)return{width:s,height:r};const o=Math.min(t/s,t/r);return{width:Math.round(s*o),height:Math.round(r*o)}}supportsWebP(){return 0===document.createElement("canvas").toDataURL("image/webp").indexOf("data:image/webp")}createPreviewUrl(e){const t=URL.createObjectURL(e);return this.previewUrls||(this.previewUrls=new Set),this.previewUrls.add(t),t}revokePreviewUrl(e){e?.startsWith("blob:")&&(URL.revokeObjectURL(e),this.previewUrls?.delete(e))}maybeLockUploads(e){const t=this.fields.get(e);if(!t?.ui?.dropZone)return;if("post_group"===t.config.destination)return;const s=t.uploads?.size||0,r=t.config?.maxFiles||999;t.ui.dropZone.hidden=s>=r,t.element.classList.toggle("at-max-uploads",s>=r)}createUploadElement(e,t=!1){let s=window.getTemplate("uploadItem");if(!s)return void console.error("Image template not found");s.dataset.uploadId=e.id,e.originalFile&&(s.dataset.subtype=this.getSubtypeFromMime(e.originalFile.type)),s.querySelector('[name="featured"]').value=e.id;let[r,o,i,a,l]=[s.querySelector('[name="featured"]'),s.querySelector("img"),s.querySelector("video"),s.querySelector("label > span"),s.querySelector("details")];switch([r.value,o.src,o.alt]=[e.id,e.preview,e.originalFile?.name??e.meta?.originalName??""],s.dataset.subtype){case"image":[o.src,o.alt]=[e.preview,e.originalFile?.name??e.meta?.originalName??""],i.remove(),a.remove();break;case"video":i.src=e.preview,o.remove(),a.remove();break;case"document":const t=e.originalFile?.name??e.meta?.originalName??"",s=t.split(".").pop()?.toLowerCase()??"",r={pdf:"file-pdf",csv:"file-csv",doc:"file-doc",docx:"file-doc",txt:"file-txt",xls:"file-xls",xlsx:"file-xls"},l=window.getIcon(r[s]||"file");a.innerText=e.originalFile.name,a.prepend(l),o.remove(),i.remove()}if(l){let e=window.getTemplate("uploadMeta");e&&l.append(e)}return s.draggable=t,s.querySelectorAll("input").forEach((t=>{let s=t.id;if(s){let r=s+e.id,o=t.parentNode.querySelector(`label[for="${s}"]`);t.id=r,o&&(o.htmlFor=r)}})),s}async submitUploads(e){const t=this.fields.get(e);if(!t?.uploads||0===t.uploads.size)return;let s=Array.from(t.uploads);if(0===s.length)return void this.error.log("No uploads to upload",{component:"UploadManager",action:"submitGroupedUploads",fieldId:e});const r=this.getFieldGroups(e);if(0===r.length)return void this.error.log("No groups created for post_group upload",{component:"UploadManager",action:"submitGroupedUploads",fieldId:e});const o=[],i=new FormData;let a=[];s=s.map((e=>this.uploads.get(e))),r.forEach(((e,t)=>{const r={images:[],fields:{}};for(let[t,s]of Object.entries(e.changes))r.fields[t]=s;s.filter((t=>t.groupId===e.id)).forEach((e=>{if(e){const t=e.processedFile||e.originalFile;if(t){i.append("files[]",t);const s={upload_id:e.id,index:a.length};r.images.push(s),a.push(e.id)}}})),o.push(r)})),s.filter((e=>!Object.hasOwn(e,"groupId"))).forEach((e=>{if(e){const t={images:[],fields:{}},s=e.processedFile||e.originalFile;if(s){i.append("files[]",s);const r={upload_id:e.id,index:a.length};t.images.push(r),a.push(e.id)}o.push(t)}})),i.append("content",t.config.content),i.append("user",t.config.itemID),i.append("posts",JSON.stringify(o)),i.append("upload_ids",JSON.stringify(a));const l={endpoint:"uploads/groups",method:"POST",data:i,title:`Creating ${o.length} ${t.config.content}${o.length>1?"s":""} from uploads...`,popup:`Creating ${o.length} post${o.length>1?"s":""}...`,canMerge:!1,headers:{action_nonce:jvbSettings.dash},append:"_upload"};try{const e=await this.queue.addToQueue(l);return s.forEach((t=>{let s=this.uploads.get(t);s&&(s.operationId=e,this.updateUploadStatus(t,"queued"))})),t.operationId=e,this.a11y.announce(`Creating ${o.length} post${o.length>1?"s":""} from your uploads`),e}catch(t){throw this.error.log(t,{component:"UploadManager",action:"submitGroupedUploads",fieldId:e}),t}finally{this.schedulePersistance(t.id)}}async queueUpload(e){const t=this.fields.get(e);if(!t?.uploads)return;const s=Array.from(t.uploads);if(0===s.length)return;const r=this.prepareUploadData(t,s);this.a11y.announce("Queuing for upload");let o=1===s.length?"file":"files";const i={endpoint:"uploads",method:"POST",data:r,title:`Uploading ${s.length} ${o} to server...`,popup:`Uploading ${s.length} ${o}...`,canMerge:!1,headers:{action_nonce:jvbSettings.dash},append:"_upload"};try{const e=await this.queue.addToQueue(i);return s.forEach((t=>{let s=this.uploads.get(t);s&&(s.operationId=e,this.updateUploadStatus(t,"queued"))})),t.operationId=e,e}catch(e){throw e}finally{this.schedulePersistance(t.id)}}prepareUploadData(e,t){const s=new FormData;s.append("content",e.config.content),s.append("mode",e.config.mode),s.append("field_name",e.config.name),s.append("fieldId",e.id),s.append("field_type",e.config.type),s.append("subtype",e.config.subtype),s.append("item_id",e.config.itemID),s.append("destination",e.config.destination||"meta");let r=[];const o=this.getFieldGroups(e.id);if("post_group"===e.config.destination&&o.length>0){let e=[],t=[],i=[];o.forEach((o=>{let a=[],l=null;o.uploads.forEach((e=>{let t=this.uploads.get(e);if(t){const e=t.processedFile||t.originalFile;if(e){s.append("files[]",e);r.length;r.push(t.id),a.push(t.id);const o=t.element?.querySelector('[name="featured"]');o?.checked&&(l=t.id)}}})),e.push(a),t.push(o.title||""),i.push(l)})),s.append("groups",JSON.stringify(e)),s.append("group_titles",JSON.stringify(t)),s.append("featured_images",JSON.stringify(i))}else t.forEach((e=>{let t=this.uploads.get(e);if(t){const e=t.processedFile||t.originalFile;e&&(s.append("files[]",e),r.push(t.id))}}));return s.append("upload_ids",JSON.stringify(r)),s}getFieldGroups(e){const t=[];return this.groups.forEach(((s,r)=>{if(s.fieldId===e){const o=this.fields.get(e),i=o?.ui?.groups?.groups?.get(r);t.push({id:r,uploads:Array.from(s.uploads||new Set),changes:s.changes||{},element:i||null})}})),t}async queueUploadMeta(e){const t=this.getUploadFromElement(e.target);if(!t)return;if(!this.fields.get(t.fieldId))return;if(!e.target.closest(".upload-meta"))return;let s={};s[e.target.name]=e.target.value,t.meta={...t.meta,...s};let r={};r[t.attachmentId??t.id]=t.meta;const o={endpoint:"uploads/meta",method:"POST",data:r,title:"Updating meta",canMerge:!0,headers:{action_nonce:jvbSettings.dash}};try{await this.queue.addToQueue(o)}catch(e){this.error.log(e,{component:"UploadManager",action:"sendMetaUpdate",uploadId:t.id})}}createGroup(e,t=null){const s=this.fields.get(e);if(!s)return console.error("Field not found:",e),null;t||(t=`group_${Date.now()}_${Math.random().toString(36).substr(2,9)}`);const r=this.createGroupElement(t,e);if(!r)return console.error("Failed to create group element"),null;s.ui.groups||(s.ui.groups={groups:new Map,container:null,empty:null,display:null}),s.ui.groups.groups.set(t,r),s.ui.groups.container&&s.ui.groups.empty?s.ui.groups.container.insertBefore(r,s.ui.groups.empty):s.ui.groups.container&&s.ui.groups.container.appendChild(r);const o={id:t,fieldId:e,element:r,grid:r.querySelector(".item-grid.group"),uploads:new Set,changes:{}};return this.groups.set(t,o),this.addGroupSelectionHandler(e,t),this.schedulePersistance(e),o}createGroupElement(e,t){let s=window.getTemplate("imageGroup");if(!s)return;s.dataset.groupId=e,s.dataset.fieldId=t;let r=window.getTemplate("groupMetadata");const o=s.querySelector(".fields");if(o&&r){o.append(r);const i=o.querySelector('[name="post_title"]'),a=o.querySelector('[name="post_excerpt"]');i&&(i.id=`${e}_title`,i.name=`${e}[post_title]`),a&&(a.id=`${e}_excerpt`,a.name=`${e}[post_excerpt]`);let l=this.fields.get(t);if(""!==l.config.content){s.querySelector("summary").textContent=l.config.content+" Fields"}}else s.querySelector("details").remove();const i=s.querySelector(".item-grid.group");return i&&(i.dataset.groupId=e),s}deleteGroup(e,t=!0){let s=this.groups.get(e);if(!s)return;let r=!0;t&&s.uploads&&s.uploads.size>0&&(r=!window.confirm("Delete uploads in group?")),t&&r&&s.uploads&&s.uploads.size>0&&Array.from(s.uploads).forEach((e=>{this.addImageToGroup(e,null,!1)})),this.groups.delete(e);let o=s.element;o&&(o.remove(),this.a11y.announce("Group removed")),this.schedulePersistance(s.fieldId)}addToGroup(e,t=null,s=!0){let r=this.uploads.get(e);if(!r)return;let o=this.fields.get(r.fieldId);if(!o)return;if(!t&&r.location===o.ui.preview||t===r.location)return;if(r.location){let t=r.location.dataset.groupId;if(t){let s=this.groups.get(t);s&&s.uploads&&(s.uploads.delete(e),0===s.uploads.size&&this.deleteGroup(t))}}const i=r.element.querySelector('[name*="select-item"]');i&&(i.checked=!1);let a=r.element.querySelector('[name="featured"]');if(a.hidden=!t,t){if(!t.classList.contains("item-grid")||!t.classList.contains("preview")){let s=t.dataset.groupId;a.name=s+"_"+a.name;let o=this.groups.get(s);o||(o=this.createGroup(r.fieldId),t=o.grid,s=o.id),o&&(o.uploads.add(e),r.groupId=s)}}else t=o.ui.preview,r.groupId=null;r.location=t,t.append(r.element),s&&this.schedulePersistance(o.id)}removeFromGroup(e){const t=this.uploads.get(e);if(!t)return;const s=this.fields.get(t.fieldId);if(!s)return;if(t.groupId){const s=this.groups.get(t.groupId);s?.uploads&&(s.uploads.delete(e),0===s.uploads.size&&this.deleteGroup(t.groupId,!1)),t.groupId=null}s.ui?.preview&&(s.ui.preview.appendChild(t.element),t.location=s.ui.preview);const r=t.element.querySelector('[name="featured"]');r&&(r.hidden=!0,r.checked=!1)}removeUpload(e,t){const s=this.fields.get(e),r=this.uploads.get(t);if(!s||!r)return;if(s.uploads?.delete(t),r.groupId){const e=this.groups.get(r.groupId);e&&e.uploads&&(e.uploads.delete(t),0===e.uploads.size&&this.removeGroup(r.groupId))}r.element?.remove(),this.clearUpload(t),this.updateFieldState(e),this.maybeLockUploads(e);const o=this.selectionHandlers.get(s.id);o&&o.deselect(t),this.a11y.announce("Upload removed")}schedulePersistance(e){const t=`persist_${e}`;window.debouncer.schedule(t,(()=>this.persistFieldState(e)),1e3)}async persistFieldState(e){const t=this.fields.get(e);if(!t)return;const s={...t,id:e,fieldId:e,uploads:Array.from(t.uploads||[]).map((e=>this.uploads.get(e))),groups:Array.from(this.groups.entries()).filter((([t,s])=>s.fieldId===e&&s.uploads&&s.uploads.size>0)).map((([e,t])=>({id:t.id,uploads:Array.from(t.uploads),changes:t.changes||{}}))),context:{url:this.normalizeUrl(window.location.href),fullUrl:window.location.href,modalType:this.getModalType(t),formId:t.formId,fieldSelector:`.field.upload[data-field="${t.config.name}"]`},timestamp:Date.now()};await this.fieldStore.save(s)}normalizeUrl(e){try{const t=new URL(e);return t.origin+t.pathname}catch(t){return e}}getFieldUploads(e,t=!1){const s=this.fields.get(e);return s&&s.uploads?Array.from(s.uploads).map((e=>{const s=this.uploads.get(e);return s?t?{id:s.id,fieldId:s.fieldId,status:s.status,attachmentId:s.attachmentId,operationId:s.operationId,groupId:s.groupId||null,changes:s.changes||{},meta:{originalName:s.meta?.originalName||s.originalFile?.name,size:s.meta?.size||s.originalFile?.size,type:s.meta?.type||s.originalFile?.type,title:s.meta?.title,alt:s.meta?.alt,caption:s.meta?.caption}}:s:null})).filter(Boolean):[]}async checkForStoredUploads(){if(!this.db)return;const e=this.db.transaction(["fieldStates"],"readonly").objectStore("fieldStates"),t=(await new Promise((t=>{const s=e.getAll();s.onsuccess=()=>t(s.result)}))).filter((e=>e.uploads.some((e=>!e.operationId&&("completed"===e.status||"processed"===e.status||"local_processing"===e.status||"processed-original"===e.status)))));0!==t.length&&this.showRecoveryNotification(t)}async handleRestoreUploads(){let e=document.querySelector("dialog.restore-uploads");if(!e)return;const t=this.getSelectedRestorationUploads(e);0!==t.length&&(await this.restoreSelectedUploads(t),this.cleanupRestore())}getSelectedRestorationUploads(e){let t=[];return e.querySelectorAll("[type=checkbox]:checked").forEach((e=>{const s=e.closest(".item");s&&t.push({uploadId:s.dataset.uploadId,fieldId:s.dataset.fieldId})})),t}handleGroupMetaChange(e){let t=this.getGroupFromElement(e);if(!t)return;Object.hasOwn(t,"changes")||(t.changes={});let s=e.name;if(s.includes("group")){let e=t.id+"_",r=t.id+"[";s=s.replace(e,"").replace(r,"").replace("]","")}t.changes[`${s}`]=e.value,this.groups.set(t.id,t),this.schedulePersistance(t.fieldId)}async showRecoveryNotification(e){const t=e.reduce(((e,t)=>e+t.uploads.length),0),s=e.reduce(((e,t)=>e+(t.groups?.length||0)),0);let r,o=window.getTemplate("restoreNotification");if(!o)return void console.error("Restore notification template not found");if(s>0){r=`${s} ${s>1?"groups":"group"} with ${t} ${t>1?"uploads":"upload"} can be restored.`}else r=`${t} upload(s) from ${e.length} field(s) can be recovered.`;const i=o.querySelector(".restore-details");i&&(i.textContent=r);for(const t of e){let e=window.getTemplate("restoreField");if(!e)continue;const s=e.querySelector("h3");s&&(s.textContent=t.config.name||"Unnamed Field");const r=e.querySelector(".item-grid.restore");for(const e of t.uploads){let s=window.getTemplate("uploadItem");if(!s)continue;const o=await this.uploadStore.getBlob(e.id);if(o)try{const r=new Blob([o.data],{type:o.type}),i=this.createPreviewUrl(r);let[a,l,n,d,c]=[s.querySelector('[name="featured"]'),s.querySelector("img"),s.querySelector("video"),s.querySelector("label > span"),s.querySelector("details")];s.dataset.uploadId=e.id,s.dataset.fieldId=t.id;let u=this.getSubtypeFromMime(o.type);switch(s.dataset.subtype=u,u){case"image":[l.src,l.alt]=[i,e.originalFile?.name??e.meta?.originalName??""],n.remove(),d.remove();break;case"video":n.src=i,l.remove(),d.remove();break;case"document":let t;switch(""){case"pdf":t=window.getIcon("file-pdf");break;case"csv":t=window.getIcon("file-csv");break;case"doc":t=window.getIcon("file-doc");break;case"txt":t=window.getIcon("file-txt");break;case"xls":t=window.getIcon("file-xls");break;default:t=window.getIcon("file")}d.innerText=e.originalFile.name,d.prepend(t),l.remove(),n.remove()}s.dataset.previewUrl=i}catch(t){console.warn("Failed to create preview for upload:",e.id,t)}const i=s.querySelector("summary span");i&&(i.textContent=e.meta?.originalName||"Unknown file");const a=s.querySelector("details");a&&e.meta&&(a.textContent=`${this.formatBytes(e.meta.size)} • ${e.meta.type}`),s.querySelectorAll("input").forEach((t=>{let s=t.id;if(s){let r=s+e.id,o=t.parentNode.querySelector(`label[for="${s}"]`);t.id=r,o&&(o.htmlFor=r)}})),r&&r.appendChild(s)}o.querySelector(".wrap").appendChild(r)}document.querySelector(".field.upload").appendChild(o),o=document.querySelector("dialog.restore-uploads"),this.restoreModal=new window.jvbModal(o),this.restoreSelection=new window.jvbHandleSelection({container:o,ui:{selectAll:o.querySelector("#select-all-restore"),count:o.querySelector(".selection-count")}}),this.restoreModal.handleOpen()}async restoreSelectedUploads(e){const t=new Map;if(e.forEach((e=>{t.has(e.fieldId)||t.set(e.fieldId,[]),t.get(e.fieldId).push(e.uploadId)})),!this.db)return;const s=this.db.transaction(["fieldStates"],"readonly").objectStore("fieldStates");for(const[e,r]of t.entries()){const t=s.get(e),o=await new Promise((e=>{t.onsuccess=()=>e(t.result),t.onerror=()=>e(null)}));o&&(o.uploads=o.uploads.filter((e=>r.includes(e.id))),await this.restoreField(o))}}async restoreField(e){const{config:t,context:s,uploads:r,groups:o,id:i}=e;s.modalType&&await this.openModalForRestore(s);let a=document.querySelector(`.field.upload[data-field="${t.name}"]`);if(!a){const e=`${t.content}_${t.itemID}_${t.name}`;a=document.querySelector(`.field.upload[data-uploader="${e}"]`)}if(!a)return void console.warn(`Field ${t.name} not found for restoration`,t);let l=a.dataset.uploader;l&&this.fields.has(l)||(l=this.registerUploader(a,t));const n=this.fields.get(l);if(n){n.state=e.state||"ready",n.ui=this.buildFieldUI(a),n.ui.groups?.display&&(n.ui.groups.display.hidden=!1),o&&o.length>0&&await this.restoreGroups(l,o);for(const e of r)await this.restoreUpload(n,e);this.updateFieldState(l),this.maybeLockUploads(l),"direct"===t.mode&&"post_group"!==t.destination&&await this.queueUpload(l)}else console.error("Failed to register field for restoration")}async restoreUpload(e,t){const s=await this.uploadStore.getBlob(t.id);if(!s)return void console.warn("Blob data not found for upload:",t.id);{const e=s.data instanceof File?s.data:new File([s.data],s.name,{type:s.type,lastModified:s.lastModified});t.originalFile=e,t.processedFile=e,t.preview=this.createPreviewUrl(e)}e.uploads||(e.uploads=new Set),e.uploads.add(t.id);const r=this.getSubtypeFromMime(t.originalFile.type);let o;if(t.element=this.createUploadElement({...t,subtype:r},"post_group"===e.config.destination),o=t.groupId&&e.ui.groups.groups.has(t.groupId)?e.ui.groups.groups.get(t.groupId).querySelector(".item-grid"):e.ui.preview,o&&(o.appendChild(t.element),t.location=o),this.uploads.set(t.id,t),t.groupId){const e=this.groups.get(t.groupId);e&&e.uploads&&e.uploads.add(t.id)}}async restoreGroups(e,t){for(const s of t){const t=this.createGroup(e,s.id);if(t&&(s.meta&&(t.meta={...s.meta}),s.changes&&(t.changes={...s.changes}),s.title)){const e=t.element.querySelector('[name*="post_title"]');e&&(e.value=s.title)}}}async openModalForRestore(e){const{modalType:t,formId:s}=e;let r=null;switch(t){case"create":r=document.querySelector('[data-action="create"]');break;case"edit":r=document.querySelector(`[data-action="edit"][data-id="${e.itemId}"]`);break;case"bulkEdit":r=document.querySelector('[data-action="bulk-edit"]')}r&&(r.click(),await new Promise((e=>setTimeout(e,300))))}handleFieldStoreEvent(e,t){switch(e){case"data-loaded":break;case"item-saved":console.log(`Field state saved: ${t.key}`)}}handleUploadStoreEvent(e,t){switch(e){case"data-loaded":this.checkForStoredUploads();break;case"item-saved":this.showSaveIndicator(t.key)}}async saveUpload(e){if(e.file instanceof File||e.file instanceof Blob){await this.uploadStore.saveBlob(e.id,e.file);const{file:t,originalFile:s,...r}=e;await this.uploadStore.save(r)}else await this.uploadStore.save(e)}async loadFields(){(await this.fieldStore.getAll()).forEach((e=>{e.uploads&&Array.isArray(e.uploads)&&(e.uploads=new Set(e.uploads.map((e=>e.id)))),this.fields.set(e.fieldId,e)}))}async loadUploads(){(await this.uploadStore.getAll()).forEach((e=>{this.uploads.set(e.id,e)}))}subscribe(e){return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((s=>s(e,t)))}destroy(){document.removeEventListener("click",this.clickHandler),document.removeEventListener("change",this.changeHandler),document.removeEventListener("dragenter",this.dragEnterHandler),document.removeEventListener("dragleave",this.dragLeaveHandler),document.removeEventListener("dragover",this.dragOverHandler),document.removeEventListener("drop",this.dropHandler),this.dragController&&this.dragController.destroy(),this.selectionHandlers.forEach((e=>e.destroy())),this.selectionHandlers.clear(),this.cleanupAllPreviewUrls(),this.fields.clear(),this.uploads.clear(),this.groups.clear(),this.selected.clear(),this.subscribers.clear()}cleanupRestore(){this.restoreModal.handleClose(),this.restoreSelection.destroy(),this.restoreSelection=null,this.restoreModal.destroy(),this.restoreModal.modal.remove(),this.restoreModal=null}async cleanupStoredUploads(){this.fieldStore.clear(),this.uploadStore.clear()}async clearField(e){await this.fieldStore.delete(e);const t=this.fields.get(e);if(t?.uploads)for(const e of t.uploads)await this.uploadStore.delete(e);this.fields.delete(e)}async clearUpload(e,t=!0){const s=this.uploads.get(e);if(s){if(this.revokePreviewUrl(s.preview),s.element){const e=s.element.dataset.previewUrl;this.revokePreviewUrl(e),delete s.element.dataset.previewUrl}t&&await this.schedulePersistance(s.fieldId),this.uploads.delete(e),this.uploadStore.delete(e),this.uploadStore.delete(e,"blobs")}}cleanupAllPreviewUrls(){this.previewUrls&&(this.previewUrls.forEach((e=>{try{URL.revokeObjectURL(e)}catch(e){}})),this.previewUrls.clear())}}document.addEventListener("DOMContentLoaded",(()=>{window.jvbUploads=new e}))})(); |