Jake Vanderwerf
4 hours ago 56a9a1ccf764ff7a6af8f8a2292cb07443cb4aa7
assets/js/min/queue.min.js
@@ -1 +1 @@
(()=>{class e{constructor(e={}){if(this.canUpdateUI=!0,this.config={apiBase:jvbSettings.api,maxRetries:3,pollInterval:5e3,activityDelay:2e3,autosync:!0,endpoint:"queue",...e},this.isProcessing=!1,this.isPolling=!1,this.subscribers=new Set,this.statuses=["queued","localProcessing","uploading","pending","processing","completed","failed","failed_permanent"],this.user=window.auth.getUser(),!this.user)return console.log("Queue: User not logged in, queue disabled"),this.store=null,void(this.canUpdateUI=!1);this.headers={"X-WP-Nonce":window.auth.getNonce(),...e.headers},this.a11y=window.jvbA11y,this.errors=window.jvbError;const t=window.jvbStore.register("queue",{storeName:"queue",keyPath:"id",endpoint:this.config.endpoint,TTL:1/0,indexes:[{name:"status",keyPath:"status"},{name:"type",keyPath:"type"}],showLoading:!1,delayFetch:!1});this.store=t.queue,this.classes=["offline","synced","pending"],this.initUI(),this.initListeners(),this.ui.panel&&(this.popup=new window.jvbPopup({popup:this.ui.panel,toggle:this.ui.toggle,name:"Queue Panel"})),this.updateUI=()=>window.debouncer.schedule("queue-ui-update",this._updateUI.bind(this),100),this.initQueue()}async initQueue(){const e=this.getOperationsByStatus(["completed","failed_permanent"],!1);e.length>0?this.startPolling():this.updateStatusPanel("synced"),this.store.subscribe(((e,t)=>{switch(e){case"data-loaded":case"items-saved":this.getOperationsByStatus(["completed","failed_permanent"],!1).length>0&&this.startPolling(),this.updateUI();break;case"item-saved":if(t.item){const e=this.store.data.get(t.item.id);e&&e.status!==t.item.status&&this.handleOperationStatusChange(t.item,e.status)}this.hasQueuedOperations()&&this.startPolling();break;default:this.updateUI()}})),this.notify("queue-initialized",{operations:e})}handleOperationStatusChange(e,t){if(e&&t!==e.status)switch(e.status){case"completed":this.notify("operation-completed",e);break;case"failed":this.notify("operation-failed",e);break;case"failed_permanent":this.notify("operation-failed-permanent",e)}}addToQueue(e){const t={id:`u${this.user}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,endpoint:null,method:"POST",headers:{},data:{},canMerge:!0,popup:"Saving changes...",title:"Operation",status:"queued",timestamp:Date.now(),retries:0,user:this.user,...e};if(t.headers={...this.headers,...t.headers},!t.endpoint||!t.data)return console.error("Invalid operation queued: missing endpoint or data"),null;const s=Array.from(this.store.data.values()).filter((e=>"queued"===e.status&&e.endpoint===t.endpoint&&e.canMerge));if(s.length>0){const e=s[0];return e.data=window.deepMerge(e.data,t.data),e.timestamp=Date.now(),this.updateOperationStatus(e.id,e.status),this.updateUI(),this.startActivityTracking(),e.id}return console.log("Added to Queue: ",t),this.store.clearCache(),this.setQueue(t),this.updateOperationStatus(t.id,t.status),this.updateUI(),this.startActivityTracking(),t.id}setQueue(e){this.store.save(e)}updateOperationStatus(e,t){let s=this.store.get(e);s&&(s.status=t,this.notify("operation-status",s),this.updateOperationUI(s))}getQueue(e){return this.store.get(e)}clearQueue(e){this.store.get(e);this.store.delete(e)}startActivityTracking(){if(!this.activityListeners){const e=["mousedown","mousemove","keypress","scroll","touchstart"];this.activityListeners=e.map((e=>{const t=()=>this.resetActivityTimer();return document.addEventListener(e,t,{passive:!0}),{event:e,handler:t}}))}this.resetActivityTimer()}resetActivityTimer(){this.lastActivity=Date.now(),this.activityTimer&&clearTimeout(this.activityTimer),this.activityTimer=setTimeout((()=>{this.processQueue()}),this.config.activityDelay)}stopActivityTracking(){this.activityTimer&&(clearTimeout(this.activityTimer),this.activityTimer=null),this.activityListeners&&(this.activityListeners.forEach((({event:e,handler:t})=>{document.removeEventListener(e,t)})),this.activityListeners=null)}hideQueue(){this.ui.panel.hidden=!0,this.ui.toggle.hidden=!0}showQueue(){this.ui.panel.hidden=!1,this.ui.toggle.hidden=!1}setProcessing(e){this.isProcessing=e,this.ui.toggle.classList.toggle("saving",e)}async processQueue(){if(this.isProcessing)return;const e=this.getOperationsByStatus("queued");if(0===e.length)return void this.stopActivityTracking();this.setProcessing(!0);for(const t of e)await this.processOperation(t);this.setProcessing(!1),this.stopActivityTracking();this.getOperationsByStatus(["queued","completed","failed_permanent"],!1).length>0?(this.startPolling(),this.showQueue()):this.hideQueue()}async processOperation(e){try{this.updateOperationStatus(e.id,"uploading"),e.data?._isFormData&&(e.data=await this.store.objectToFormData(e.data));const t=`${this.config.apiBase}${e.endpoint}`;let s;e.data instanceof FormData?(e.data.append("id",e.id),e.data.append("user",this.user),s=e.data):(s=JSON.stringify({...e.data,id:e.id,user:this.user}),e.headers["Content-Type"]="application/json");const i=await fetch(t,{method:e.method,headers:e.headers,body:s}),a=await i.json();if(!i.ok||!1===a.success)throw new Error(a.message||`HTTP ${i.status}`);if(a.id&&e.id!==a.id){const t=this.getQueue(a.id);t?(t.data=window.deepMerge(t.data,e.data),t.status=a.status||"pending",t.serverData=a,this.updateOperationStatus(t.id,t.status),this.setQueue(t),this.removeOperationFromUI(e.id),e=t):(this.clearQueue(e.id),e.id=a.id,e.status=a.status||"pending",e.serverData=a,this.updateOperationStatus(e.id,e.status),this.setQueue(e))}else e.status=a.status||"pending",e.serverData=a,this.updateOperationStatus(e.id,e.status),this.setQueue(e);this.a11y.announce(`${e.title} sent to server for processing.`)}catch(t){console.error("Operation failed:",t),e.retries++,e.lastError=t.message,e.retries>=this.config.maxRetries?e.status="failed_permanent":(e.status="failed",e.nextRetry=Date.now()+1e3*Math.pow(2,e.retries)),this.updateOperationStatus(e.id,e.status),this.setQueue(e)}}startPolling(){this.isPolling||(this.isPolling=!0,this.updateStatusPanel("pending"),this.pollTimer=setInterval((async()=>{try{this.store.clearCache(),await this.store.fetch();0===this.getOperationsByStatus(["completed","failed_permanent"],!1).length&&(this.stopPolling(),this.updateStatusPanel("synced"))}catch(e){console.error("Polling error:",e)}}),this.config.pollInterval))}stopPolling(){this.isPolling&&(this.isPolling=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.countdownTimer&&(clearInterval(this.countdownTimer),this.countdownTimer=null))}async updateServerOperations(e,t){if(0===(e=(e=Array.isArray(e)?e:e.includes(",")?e.split(","):[e]).filter((e=>{let s=this.getQueue(e);return this.getAllowedActions(s.status).includes(t)}))).length)return;const s=["cancel","dismiss"].includes(t);s&&e.forEach((e=>this.removeOperationFromUI(e)));try{const i=await fetch(`${this.config.apiBase}${this.config.endpoint}`,{method:"POST",headers:{"Content-Type":"application/json",...this.headers},body:JSON.stringify({ids:e,action:t,user:window.auth.getUser()})});if(!i.ok)throw new Error(`${t} failed: ${i.status}`);const a=await i.json();if(!a.success)throw new Error(a.message||`${t} operation failed`);return e.forEach((e=>{let i=this.getQueue(e);this.notify(`${t}-operation`,i),s?this.clearQueue(e):(i.status="queued",i.retries=0,this.setQueue(i),this.updateOperationStatus(i.id,i.status))})),"retry"===t&&this.startActivityTracking(),this.updateUI(),a}catch(s){return await window.jvbError.log(s,{component:"QueueManager",operation:"performQueueAction",action:t,operationIds:e,itemCount:e.length},(()=>this.updateServerOperations(e,t))),{success:!1,error:s.message}}}getAllowedActions(e){return{queued:["cancel"],localProcessing:["cancel"],pending:["cancel"],processing:[],completed:["dismiss"],failed:["retry","dismiss"],failed_permanent:["dismiss"]}[e]||[]}initListeners(){this.clickHandler=this.handleClick.bind(this),document.addEventListener("click",this.clickHandler),this.handleOnline=()=>{this.updateStatusPanel(),this.hasQueuedOperations()&&this.processQueue()},this.handleOffline=()=>this.updateStatusPanel("offline"),this.handleBeforeUnload=e=>{if(this.getOperationsByStatus(["queued","uploading"]).length>0)return e.preventDefault(),"You have unsaved changes in the queue."},window.addEventListener("online",this.handleOnline),window.addEventListener("offline",this.handleOffline),window.addEventListener("beforeunload",this.handleBeforeUnload)}handleClick(e){if(e.target.closest(this.selectors.panel,this.selectors.toggle))if(e.target.closest(this.selectors.refreshButton))this.store.clearCache(),this.store.clearHttpHeaders(),this.store.fetch();else if(e.target.closest(this.selectors.clearButton)){const e=this.getOperationsByStatus("completed");if(e.length>0){const t=e.map((e=>e.id));this.updateServerOperations(t,"dismiss")}}else if(e.target.closest(this.selectors.retryButton)){const e=this.getOperationsByStatus("failed");if(e.length>0){const t=e.map((e=>e.id));this.updateServerOperations(t,"retry")}}else if(e.target.closest("[data-action]")){const t=e.target.closest("[data-action]"),s=t.closest("[data-id]")?.dataset.id;s&&this.updateServerOperations(s,t.dataset.action)}else if(e.target.closest(".filters [data-filter]")){const t=e.target.closest("[data-filter]").dataset.filter;this.setFilter(t)}}initUI(){this.icons={queued:"arrows-clockwise",localProcessing:"arrows-clockwise",uploading:"syncing",pending:"cloud",processing:"syncing",completed:"cloud-check",failed:"cloud-warning",failed_permanent:"cloud-warning"},this.selectors={panel:"aside#queue",toggle:"button.qtoggle",refreshButton:"button.refreshNow",countdown:".countdown",indicator:".qtoggle .indicator",count:".qtoggle .count",popup:".popup",itemsContainer:".qitems",clearButton:".dismiss-all",retryButton:".retry-all",filters:{all:'.filters [data-filter="all"]',received:'.filters [data-filter="queued"]',localProcessing:'.filters [data-filter="localProcessing"]',uploading:'.filters [data-filter="uploading"]',pending:'.filters [data-filter="pending"]',processing:'.filters [data-filter="processing"]',completed:'.filters [data-filter="completed"]',failed:'.filters [data-filter="failed"]'}},this.ui=window.uiFromSelectors(this.selectors),this.ui.panel||(this.canUpdateUI=!1)}_updateUI(){if(!this.canUpdateUI)return;const e=Array.from(this.store.data.values()),t=this.store.lastResponse?.queue_stats||{queued:0,localProcessing:0,uploading:0,pending:0,processing:0,completed:0,failed:0,failed_permanent:0};if(this.ui.count){const s=e.length-t.completed;this.ui.count.textContent=s>0?s:"",this.ui.count.style.display=s>0?"":"none"}if(this.ui.indicator){const e=t.queued>0||t.uploading>0||t.pending>0||t.processing>0;this.ui.indicator.classList.toggle("active",e)}this.ui.clearButton.disabled=0===this.getOperationsByStatus("completed").length,this.ui.retryButton.disabled=0===this.getOperationsByStatus("failed").length&&0===this.getOperationsByStatus("failed_permanent").length,Object.entries(this.ui.filters).forEach((([s,i])=>{const a="all"===s?e.length:t[s]||0,n=i.querySelector(".count");n&&(n.textContent=a>0?a:""),i.setAttribute("data-count",a)})),this.renderOperations()}getStatusLabel(e){return{queued:"Queued",localProcessing:"Processing locally",uploading:"Uploading",pending:"Waiting on server",processing:"Processing",completed:"Completed",failed:"Failed (will retry)",failed_permanent:"Failed permanently"}[e]||e}getItemMessage(e){if(e.message)return e.message;if(e.error_message)return e.error_message;switch(e.status){case"queued":return"Waiting to send...";case"uploading":return"Sending to server...";case"pending":return e.position?`Position ${e.position} in queue`:"In server queue";case"processing":return e.progress?`${e.progress}% complete`:"Processing...";case"completed":return"Successfully completed";case"failed":return`Failed: ${e.lastError||"Unknown error"} (Retry ${e.retries}/${this.config.maxRetries})`;case"failed_permanent":return`Failed: ${e.lastError||"Unknown error"}`;default:return""}}calculateProgress(e){if(e.progress)return e.progress;return{queued:10,uploading:25,pending:40,processing:70,completed:100,failed:0,failed_permanent:0}[e.status]||0}renderOperations(){if(!this.ui.itemsContainer)return;const e=this.store.getFiltered();if(window.removeChildren(this.ui.itemsContainer),0===e.length){let e=window.getTemplate("emptyQueue");this.ui.itemsContainer.append(e),this.a11y.announce("Nothing queued.")}else e.forEach((e=>{const t=this.createOperationUI(e);this.ui.itemsContainer.append(t)}))}createOperationUI(e){const t=window.getTemplate("queueItem");return t.dataset.id=e.id,this.updateOperationUI(e,t),t}updateOperationUI(e,t=null){t||(t=this.ui.itemsContainer?.querySelector(`[data-id="${e.id}"]`)),t||(t=this.createOperationUI(e)),this.statuses.forEach((e=>t.classList.remove(e))),t.classList.add(e.status);let s="";e.updated_at?s=window.formatTimeAgo(new Date(e.updated_at)):e.created_at&&(s=window.formatTimeAgo(new Date(e.created_at)));const i=this.calculateProgress(e),a=t.querySelector(".type"),n=t.querySelector(".status"),r=t.querySelector(".info .details"),o=t.querySelector(".info .time"),d=t.querySelector(".progress .fill");if(a&&(a.textContent=e.title),n){n.querySelector(".icon")?.remove();let t=this.getStatusLabel(e.status);n.title=t,n.prepend(window.getIcon(this.icons[e.status])),n.querySelector("span").textContent=t}r&&(r.textContent=this.getItemMessage(e)),o&&(o.textContent=s),d&&(d.style.width=`${i}%`);const l=t.querySelector(".actions");l&&this.updateActionButtons(e,l)}updateActionButtons(e,t){switch(window.removeChildren(t),e.status){case"queued":case"localProcessing":case"pending":const s=window.getTemplate("button");s.classList.add("cancel"),s.dataset.action="cancel",s.textContent="Cancel",t.appendChild(s);break;case"failed":case"failed_permanent":const i=window.getTemplate("button"),a=window.getTemplate("button");i.classList.add("retry"),i.textContent="Retry",i.disabled=e.retries>=this.maxRetries,i.dataset.action="retry",a.classList.add("dismiss"),a.textContent="Dismiss",a.dataset.action="dismiss",t.appendChild(i),t.appendChild(a);break;case"completed":const n=window.getTemplate("button");n.dataset.action="dismiss",n.classList.add("dismiss"),n.textContent="Dismiss",t.appendChild(n)}}removeOperationFromUI(e){const t=this.ui.itemsContainer?.querySelector(`[data-id="${e}"]`);t&&(t.style.opacity="0",t.style.transform="scale(0.9)",setTimeout((()=>t.remove()),300))}updateStatusPanel(e){this.ui.panel?.classList.remove(...this.classes),this.classes.includes(e)&&this.ui.panel?.classList.add(e)}setFilter(e){Object.values(this.ui.filters).forEach((t=>{t&&t.classList.toggle("active",t.dataset.filter===e)})),"all"===e?this.store.clearFilters():this.store.setFilter("status",e)}getOperationsByStatus(e,t=!0){return Array.isArray(e)||"string"!=typeof e||(e=[e]),t?Array.from(this.store.data.values()).filter((t=>e.includes(t.status))):Array.from(this.store.data.values()).filter((t=>!e.includes(t.status)))}hasQueuedOperations(){return this.getOperationsByStatus("queued").length>0}subscribe(e){if(this.subscribers)return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach((s=>s(e,t)))}destroy(){this.stopPolling(),this.stopActivityTracking(),this.clickHandler&&document.removeEventListener("click",this.clickHandler),this.keyHandler&&document.removeEventListener("keydown",this.keyHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",(async function(){window.auth.subscribe((t=>{"auth-loaded"===t&&(window.jvbQueue=new e)}))}))})();
(()=>{class e{constructor(){this.a11y=window.jvbA11y,this.error=window.jvbError,this.user=window.auth.getUser(),this.user&&(this.canUpdateUI=!0,this.isProcessing=!1,this.isPolling=!1,this.queue=new Map,this.items=new Map,this.subscribers=new Set,this.loadFromStorage=!1,this.api=jvbSettings.api,this.endpoint="queue",this.init())}init(){this.headers={"X-WP-Nonce":window.auth.getNonce()},this.initElements(),this.initListeners(),this.initStore(),this.canUpdateUI&&this.ui.panel&&(this.popup=window.jvbPopup.registerPopup({popup:this.ui.panel,toggle:this.ui.toggle.button,name:"Queue Panel"})),this.defineTemplates()}initElements(){this.panelStatuses=["syncing","synced","pending","offline"],this.statuses=["queued","localProcessing","uploading","pending","processing","completed","failed","failed_permanent"],this.pendingStatuses=["queued","localProcessing","uploading"],this.workingStatuses=["pending","processing"],this.completedStatuses=["completed","failed","failed_permanent"],this.icons={queued:"arrows-clockwise",localProcessing:"arrows-clockwise",uploading:"syncing",pending:"cloud",processing:"syncing",completed:"cloud-check",failed:"cloud-warning",failed_permanent:"cloud-warning"},this.selectors={panel:"aside#queue",toggle:{button:"button.qtoggle",indicator:".qtoggle .indicator",count:".qtoggle .count"},refresh:{button:"#queue .m-actions .refresh",countdown:"#queue .m-actions .refresh .countdown"},popup:{popup:"#queue .popup",message:"#queue .popup span"},items:{container:"#queue .qitems"},actions:{retry:"#queue .retry-all",clear:"#queue .dismiss-all"},filters:{filter:"#queue [data-filter]",all:{label:'#queue [for="qfilter-all"]',radio:'#queue [data-filter="all"]',count:'#queue [data-filter="all"] .count'},queued:{label:'#queue [for="qfilter-queued"]',input:'#queue [data-filter="queued"]',count:'#queue [for="qfilter-queued"] .count'},localProcessing:{label:'#queue [for="qfilter-localProcessing"]',input:'#queue [data-filter="localProcessing"]',count:'#queue [for="qfilter-localProcessing"] .count'},uploading:{label:'#queue [for="qfilter-uploading"]',input:'#queue [data-filter="uploading"]',count:'#queue [for="qfilter-uploading"] .count'},pending:{label:'#queue [for="qfilter-pending"]',input:'#queue [data-filter="pending"]',count:'#queue [for="qfilter-pending"] .count'},processing:{label:'#queue [for="qfilter-processing"]',input:'#queue [data-filter="processing"]',count:'#queue [for="qfilter-processing"] .count'},completed:{label:'#queue [for="qfilter-completed"]',input:'#queue [data-filter="completed"]',count:'#queue [for="qfilter-completed"] .count'},failed:{label:'#queue [for="qfilter-failed"]',input:'#queue [data-filter="failed"]',count:'#queue [for="qfilter-failed"] .count'}},item:{type:".type",status:".status",details:".info .details",icon:".status .icon",startedAt:".started time",completed:{wrap:".completed",label:".completed span",time:".completed time"},progress:{progress:".progress",fill:".progress .fill",details:".progress .details",icon:".progress .icon"},actions:{cancel:"button.cancel",retry:"button.retry",refresh:"button.refresh",dismiss:"button.dismiss"}}},this.ui=window.uiFromSelectors(this.selectors),this.ui.panel||(this.canUpdateUI=!1)}defineTemplates(){const e=window.jvbTemplates;e.define("emptyState"),e.define("queueItem",{setup({el:e,refs:t,manyRefs:s,data:i}){e.dataset.id=i.id}})}initListeners(){this.activityListeners=null,this.clickHandler=this.handleClick.bind(this),this.onlineHandler=this.handleOnline.bind(this),this.offlineHandler=this.handleOffline.bind(this),this.unloadHandler=this.handleBeforeUnload.bind(this),this.visibilityHandler=this.handleVisibilityChange.bind(this),document.addEventListener("click",this.clickHandler),window.addEventListener("online",this.onlineHandler),window.addEventListener("offline",this.offlineHandler),document.addEventListener("visibilitychange",this.visibilityHandler)}handleOnline(){this.updatePanel("synced"),this.getQueueByStatus(this.pendingStatuses).length>0&&this.processQueue()}handleOffline(){this.updatePanel("offline")}handleVisibilityChange(e){this.isPolling&&document.hidden?this.stopPolling():this.maybeStartPolling()}handleBeforeUnload(e){if(!this.ui.panel)return;return this.getQueueByStatus(this.pendingStatuses).length>0?(e.preventDefault(),e.returnValue="",""):void 0}handleClick(e){if(!window.targetCheck(e,this.selectors.panel+", "+this.selectors.toggle.button))return;if(window.targetCheck(e,this.selectors.refresh.button))return this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),this.store.clearFilters(),void this.store.fetch().finally(()=>{this.ui.refresh.button.classList.remove("fetching")});if(window.targetCheck(e,this.selectors.actions.refresh))return void this.handleRefresh(opId);if(window.targetCheck(e,this.selectors.actions.clear))return void this.opActions("completed","dismiss").then(()=>{});if(window.targetCheck(e,this.selectors.actions.retry))return void this.opActions("failed","retry").then(()=>{});const t=window.targetCheck(e,"[data-action]");if(t){const e=t.closest("[data-id]")?.dataset.id;return void(e&&this.opActions(e,t.dataset.action))}const s=window.targetCheck(e,this.selectors.filters.filter);s&&this.setFilter(s.dataset.filter)}setFilter(e){Object.values(this.ui.filters).forEach(t=>{t.input?.dataset.filter===e&&(t.input.checked=!0)}),"all"===e?this.store.clearFilters():this.store.setFilter("status",e)}trackActivity(){if(!this.activityListeners){const e=["mousedown","mousemove","keypress","scroll","touchstart"];this.activityListeners=e.map(e=>{const t=()=>this.resetActivityTimer();return document.addEventListener(e,t,{passive:!0}),{event:e,handler:t}})}this.resetActivityTimer()}resetActivityTimer(){this.activityTimer&&clearTimeout(this.activityTimer),this.activityTimer=setTimeout(()=>{this.processQueue()},1750)}stopActivityTracking(){this.activityTimer&&(clearTimeout(this.activityTimer),this.activityTimer=null),this.activityListeners&&(this.activityListeners.forEach(({event:e,handler:t})=>{document.removeEventListener(e,t)}),this.activityListeners=null)}initStore(){if(!this.user)return;const e=window.jvbStore.register("queue",{storeName:"queue",keyPath:"id",endpoint:this.endpoint,TTL:1/0,isAuth:!0,indexes:[{name:"status",keyPath:"status"},{name:"type",keyPath:"type"}],filters:{user:window.auth.getUser()},showLoading:!1});this.store=e.queue,this.store.subscribe((e,t)=>{switch(e){case"data-loaded":this.store.getAll().forEach(e=>{const t=this.queue.get(e.id),s=this.mapServerOperation(e);this.queue.set(s.id,s),t&&t.status!==s.status&&this.notify("operation-status",s)}),this.maybeStartPolling(),this.updateUI();break;case"items-save":this.maybeStartPolling(),this.updateUI();break;case"item-saved":t.item&&(this.queue.set(t.item.id,t.item),t.previousItem?.status!==t.item.status&&this.notify("operation-status",t.item)),this.maybeStartPolling()}})}handleRefresh(e){const t=this.getQueue(e);if(!t)return;let s=null;if(s={content_update:t.data?.posts?Object.values(t.data.posts)[0]?.content:null,batch_creation:t.data?.content,image_upload:"uploads",video_upload:"uploads",document_upload:"uploads"}[t.type],s&&window.jvbStore){if(window.jvbStore.stores.get(s)){window.jvbStore.clearCache(s),window.jvbStore.fetch(s);const t=this.items.get(e)?.ui?.actions?.refresh;if(t){const e=t.querySelector("span").textContent;t.querySelector("span").textContent="Refreshed!",t.disabled=!0,setTimeout(()=>{t.querySelector("span").textContent=e,t.disabled=!1},2e3)}}}else confirm("Refresh the page to see changes?")&&window.location.reload()}addToQueue(e){const t={id:`u${this.user}_${Date.now()}_${Math.random().toString(36).substring(2,9)}`,endpoint:null,method:"POST",headers:{},data:{},delay:!1,canMerge:!0,popup:"Saving changes...",title:"Operation",status:"queued",timestamp:Date.now(),created_at:(new Date).toISOString(),retries:0,user:this.user,...e};if(t.headers={...this.headers,...t.headers},!t.endpoint||!t.data)return null;if(t.popup&&this.ui.popup?.message&&(this.ui.popup.message.textContent=t.popup,this.ui.popup.popup.hidden=!1,setTimeout(()=>this.ui.popup.popup.hidden=!0,2e3)),!t.delay)return this.queue.set(t.id,t),this.processOperation(t).then(()=>{}),this.store.clearCache(),this.maybeStartPolling(),this.toggleQueue(),t.id;const s=Array.from(this.getAllQueue()).filter(e=>"queued"===e.status&&e.endpoint===t.endpoint&&e.canMerge);if(s.length>0){const e=s[0];return e.data=window.deepMerge(e.data,t.data),e.timestamp=Date.now(),this.setQueue(e),this.updateOperationStatus(e.id,e.status),this.updateUI(),this.trackActivity(),e.id}return this.store.clearCache(),this.setQueue(t),this.updateOperationStatus(t.id,t.status),this.updateUI(),this.trackActivity(),t.id}async opActions(e,t){if(this.statuses.includes(e)?e=this.getQueueByStatus(e).map(e=>e.id):"string"==typeof e&&(e=[e]),0===e.length)return;if(!["cancel","dismiss","retry"].includes(t))return;const s=["cancel","dismiss"].includes(t);s&&e.forEach(e=>{this.removeOperationUI(e)});try{const i=await window.auth.fetch(`${this.api}${this.endpoint}`,{method:"POST",headers:{"Content-Type":"application/json",...this.headers},body:JSON.stringify({action:t,ids:Array.isArray(e)?e:[e],user:this.user})});if(!i.ok)throw new Error(`${t} failed: ${i.status}`);const n=await i.json();if(!n.success)throw new Error(n.message||`${t} operation failed`);return e.forEach(e=>{let i=this.getQueue(e);if(i&&this.notify(`${t}-operation`,i),s)this.clearQueue(e);else{let t=this.getQueue(e);t.status="queued",this.setQueue(t),this.updateOperationStatus(t.id,t.status)}}),"retry"===t&&this.trackActivity(),this.updateUI(),n}catch(s){return await window.jvbError.log(s,{component:"Queue",operation:"performQueueAction",action:t,operationIds:e,itemCount:e.length},()=>this.opActions(e,t)),{success:!1,error:s.message}}}async processQueue(){if(this.isProcessing)return;const e=this.getQueueByStatus("queued");if(0===e.length)return void this.stopActivityTracking();this.setProcessing();for(const t of e)await this.processOperation(t);this.setProcessing(!1);0===this.getQueueByStatus("queued").length?this.stopActivityTracking():this.trackActivity(),this.toggleQueue(this.maybeStartPolling())}async processOperation(e){try{this.queue.has(e.id)||this.queue.set(e.id,e);let t,s,i=!1;if(e.data?._isFormData&&!e.data instanceof FormData&&(i=!0,e.data=await this.store.objectToFormData(e.data)),this.updateOperationStatus(e.id,"uploading"),e.data instanceof FormData?(e.data.append("id",e.id),e.data.append("user",window.auth.getUser()),t=e.data,s=e.data):(s={...e.data,id:e.id,user:window.auth.getUser()},t=JSON.stringify(s),e.headers["Content-Type"]="application/json"),"unknown"===e.endpoint||null==t)return;const n=await window.auth.fetch(`${this.api}${e.endpoint}`,{method:e.method,headers:e.headers,body:t});console.log("Sending request with data: ",s);const r=await n.json();if(i&&(e.data={}),console.log("Result: ",r),!n.ok||!r.success)throw new Error(r.message||`HTTP ${n.status}`);this.notify("sent-to-server",s),r.id&&e.id!==r.id?e=await this.handleServerMerge(e,r):(e.status=r.status??"pending",e.serverData=r,this.updateOperationStatus(e.id,e.status)),this.a11y.announce(`${e.title} sent to server for processing`),this.setQueue(e)}catch(t){console.error("Operation failed: ",t),e.retries++,e.lastError=t.message,e.retries>=3?e.status="failed_permanent":e.status="failed",this.updateOperationStatus(e.id,e.status),this.setQueue(e)}}async handleServerMerge(e,t){const s=this.getQueue(t.id);return s?(e.status=t.status||"pending",e.serverData=t,this.mergeOp(s,e)):(this.clearQueue(e.id),this.setQueue(t),t)}mergeOp(e,t){return e.data=window.deepMerge(e.data,t.data),e.status=t.status,Object.hasOwn(t,"serverData")&&(e.serverData=t.serverData),this.updateOperationStatus(e.id,e.status),this.removeOperationUI(t.id),this.clearQueue(t.id),e}sortByDate(e){return e.sort((e,t)=>(e.updated_at??e.timestamp??0)-(t.updated_at??t.timestamp??0))}sortOperations(e){const t={processing:0,uploading:1,pending:2,queued:3,localProcessing:4,failed:5,completed:6,failed_permanent:7};return e.sort((e,s)=>{const i=(t[e.status]??99)-(t[s.status]??99);if(0!==i)return i;const n=e.updated_at??e.timestamp??0,r=s.updated_at??s.timestamp??0;return new Date(r)-new Date(n)})}getAllQueue(){let e=new Set,t=[...Array.from(this.queue.values())];return this.loadFromStorage||(this.loadFromStorage=!0,t=[...t,...Array.from(this.store.data.values())],t=t.filter(t=>{const s=e.has(t.id);return e.add(t.id),!s})),this.sortOperations(t)}getQueueByStatus(e){return"string"==typeof e&&(e=[e]),this.getAllQueue().filter(t=>e.includes(t.status))}updateOperationStatus(e,t){let s=this.getQueue(e);s&&(this.statuses.includes(t)?(s.status=t,this.notify("operation-status",s),this.setQueue(s)):console.log("Invalid status: ",t))}setQueue(e){this.store.save(e),this.queue.set(e.id,e)}getQueue(e){return this.queue.has(e)?this.queue.get(e):this.store.get(e)}clearQueue(e){this.queue.delete(e),this.store.delete(e)}maybeStartPolling(){return this.getQueueByStatus([...this.pendingStatuses,...this.workingStatuses]).length>0?(this.startPolling(),!0):(this.updatePanel("synced"),!1)}startPolling(){this.isPolling||(this.isPolling=!0,this.updatePanel("pending"),this.runPollCycle())}async runPollCycle(){if(this.isPolling){try{if(this.ui.refresh.button.classList.add("fetching"),this.store.clearCache(),429===(await this.store.fetch()).status)return console.log("Too many requests. Waiting 30 seconds"),this.stopPolling(),void this.startCountdown(30,()=>this.runPollCycle());if(this.ui.refresh.button.classList.remove("fetching"),!this.maybeStartPolling())return this.stopPolling(),void this.updatePanel("synced")}catch(e){console.error("Polling error:",e)}this.startCountdown(5,()=>this.runPollCycle())}}startCountdown(e,t){this.ui.refresh.countdown?(this.ui.refresh.countdown.classList.add("counting"),this.ui.refresh.countdown.textContent=e,this.countdownTimer=setInterval(()=>{--e>0?this.ui.refresh.countdown.textContent=e:(this.stopCountdown(),t&&t())},1e3)):console.warn("Countdown element not found")}stopPolling(){this.isPolling&&(this.isPolling=!1,this.pollTimer&&(clearInterval(this.pollTimer),this.pollTimer=null),this.stopCountdown())}stopCountdown(){this.countdownTimer&&(clearInterval(this.countdownTimer),this.countdownTimer=null),this.ui.refresh.countdown.classList.remove("counting"),this.ui.refresh.countdown.textContent=""}updateUI(){this.canUpdateUI&&window.debouncer.schedule("queue-ui",this.handleUpdateUI.bind(this))}handleUpdateUI(){const e=this.getAllQueue();this.ui.actions.retry.disabled=0===e.filter(e=>"failed"===e.status).length,this.ui.actions.clear.disabled=0===e.filter(e=>"completed"===e.status).length;let t=e.filter(e=>[...this.pendingStatuses,...this.workingStatuses].includes(e.status));t=t.length,this.ui.toggle.count.hidden=0===t,this.ui.toggle.count.textContent=t;for(let t of this.statuses){if("failed_permanent"===t)continue;let s=e.filter(e=>e.status===t).length;this.ui.filters[t].label.hidden=0===s,this.ui.filters[t].input.dataset.count=`${s}`,this.ui.filters[t].count.textContent=s>0?s:""}this.renderOperations()}renderOperations(){if(!this.ui.items.container)return;const e=this.store.filters?.status??"all",t="all"===e?this.getAllQueue():this.getQueueByStatus(e),s=this.sortOperations(t);if(0===s.length){window.removeChildren(this.ui.items.container);const e=window.jvbTemplates.create("emptyQueue");return this.ui.items.container.append(e),void this.a11y.announce("No items in queue")}this.ui.items.container.querySelector(".empty-group")?.remove();const i=new Set(s.map(e=>e.id));this.items.forEach((e,t)=>{i.has(t)||(e.element?.remove(),this.items.delete(t))}),s.forEach((e,t)=>{let s=this.items.get(e.id);s||(s=this.createOperationElement(e)),s?.element&&(this.updateOperationUI(e.id),this.ui.items.container.append(s.element))})}createOperationElement(e){const t=window.jvbTemplates.create("queueItem",e),s={element:t,ui:window.uiFromSelectors(this.selectors.item,t)};return this.items.set(e.id,s),s}updateOperationUI(e){let t=this.items.has(e)?this.items.get(e):this.createOperationElement(e);if(!t)return;let s=this.getQueue(e),i=t.element;i.classList.remove(...this.statuses),i.classList.add(s.status);let n=this.getProgress(s);t.ui.type&&t.ui.type.textContent!==s.title&&(t.ui.type.textContent=s.title),t.ui.status&&(t.ui.status.title=this.statusLabel(s.status)),t.ui.icon&&(t.ui.icon.className=`icon icon-${this.icons[s.status]}`),t.ui.details&&(t.ui.details.textContent=this.itemMessage(s)),t.ui.startedAt&&(t.ui.startedAt.setAttribute("datetime",s.created_at),t.ui.startedAt.textContent=window.formatTimeAgo(s.created_at));s.status;const r="completed"===s.status&&(s.completed_at||s.updated_at);if(t.ui.completed.wrap.hidden=!r,r){const e=s.completed_at??s.updated_at;t.ui.completed.label.textContent="Completed: ",t.ui.completed.time.setAttribute("datetime",e),t.ui.completed.time.textContent=window.formatTimeAgo(e)}window.showProgress(t.ui.progress,n,100,this.statusLabel(s.status)),t.ui.actions.cancel&&(t.ui.actions.cancel.hidden=this.completedStatuses.includes(s.status)),t.ui.actions.retry&&(s.retries>=3&&(t.ui.actions.retry.disabled=!0),t.ui.actions.retry.hidden="failed"!==s.status),t.ui.actions.dismiss&&(t.ui.actions.dismiss.hidden=this.pendingStatuses.includes(s.status)),t.ui.actions.refresh&&(t.ui.actions.refresh.hidden="completed"!==s.status)}getProgress(e){if(void 0!==e.progress_percentage)return e.progress_percentage;if(void 0!==e.progress)return e.progress;if(!this.statuses.includes(e.status))return 0;return{queued:10,uploading:25,pending:40,processing:70,completed:100,failed:0,failed_permanent:0}[e.status]??0}removeOperationUI(e){let t=this.items.get(e);t&&window.fade(t.element,!1)}updatePanel(e="syncing"){this.ui.panel&&this.panelStatuses.includes(e)&&(this.ui.panel.classList.remove(...this.panelStatuses),this.ui.panel.classList.add(e))}statusLabel(e){if(!this.statuses.includes(e))return"";return{queued:"Queued",localProcessing:"Processing locally",uploading:"Uploading",pending:"Waiting on server",processing:"Processing",completed:"Completed",failed:"Failed",failed_permanent:"Failed permanently",merged:"Merged"}[e]}itemMessage(e){if(Object.hasOwn(e,"message")&&""!==e.message)return e.message;if(Object.hasOwn(e,"error_message")&&e.error_message)return e.error_message;switch(e.status){case"queued":return"Waiting to send...";case"uploading":return"Sending to server...";case"pending":return e.position?`Position ${e.position} in queue`:"In server queue";case"processing":if(e.count&&void 0!==e.progress_count){const t=e.progress_count,s=e.count;return`Processing ${t}/${s} items (${Math.round(t/s*100)}%)`}return void 0!==e.progress_percentage?`${e.progress_percentage}% complete`:"Processing...";case"completed":return"Successfully completed. Refresh to see changes.";case"merged":return e.merged_into?`Merged with another operation (${e.merged_into.substring(0,8)}...)`:"Merged with another operation";case"failed":return`Failed: ${e.lastError||"Unknown error"} (Retry ${e.retries}/2)`;case"failed_permanent":return`Failed: ${e.lastError||"Unknown error"}`;default:return""}}toggleQueue(e=!0){this.ui.panel&&(this.ui.panel.hidden=!e,this.ui.toggle.button.hidden=!e)}setProcessing(e=!0){this.isProcessing=e,this.ui.toggle.button.classList.toggle("saving",e)}mapServerOperation(e){const t=this.queue.get(e.id);if(t&&t.endpoint){const s={...t,...e,endpoint:t.endpoint,method:t.method,headers:t.headers,progress_percentage:e.progress_percentage,progress_count:e.progress_count,count:e.count};e.merged_into&&this.handleMergedOperation(s)}const s=e.type?e.type.replace("_update","").replace("_","/"):"unknown",i={...e,endpoint:s,method:"POST",headers:{...this.headers}};return e.merged_into&&this.handleMergedOperation(i),i}handleMergedOperation(e){e.merged_into&&(console.log(`[Queue] Operation ${e.id} merged into ${e.merged_into}`),setTimeout(()=>{this.clearQueue(e.id),this.removeOperationFromUI(e.id)},3e3))}subscribe(e){if(this.subscribers)return this.subscribers.add(e),()=>this.subscribers.delete(e)}notify(e,t){this.subscribers.forEach(s=>s(e,t))}destroy(){this.isPolling&&this.stopPolling(),this.stopActivityTracking(),document.removeEventListener("click",this.clickHandler),this.subscribers.clear()}}document.addEventListener("DOMContentLoaded",async function(){window.auth.subscribe(t=>{"auth-loaded"===t&&(window.jvbQueue=new e)})})})();