'use strict'; class ShortPixelScreen extends ShortPixelScreenBase { isCustom = true; isMedia = true; panels = []; currentPanel = 'dashboard'; debugCounter = 0; averageOptimization = 0; numOptimizations = 0; Init() { // super(MainScreen, processor); // Hook up the button and all. this.LoadPanels(); this.LoadActions(); window.addEventListener('shortpixel.processor.paused', this.TogglePauseNotice.bind(this)); window.addEventListener('shortpixel.processor.responseHandled', this.CheckPanelData.bind(this)); window.addEventListener('shortpixel.bulk.onUpdatePanelStatus', this.EventPanelStatusUpdated.bind(this)); window.addEventListener('shortpixel.bulk.onSwitchPanel', this.EventPanelSwitched.bind(this)); window.addEventListener('shortpixel.reloadscreen', this.ReloadScreen.bind(this)); var processData = ShortPixelProcessorData.startData; var initMedia = processData.media.stats; var initCustom = processData.custom.stats; var initTotal = processData.total.stats; var isPreparing = false; var isRunning = false; var isFinished = false; if (initMedia.is_preparing == true || initCustom.is_preparing == true ) isPreparing = true; else if (initMedia.is_running == true || initCustom.is_running == true ) isRunning = true; else if ( (initMedia.is_finished == true && initMedia.done > 0) || (initCustom.is_finished == true && initCustom.done > 0) ) isFinished = true; this.UpdateStats(initMedia, 'media'); // write UI. this.UpdateStats(initCustom, 'custom'); this.UpdateStats(initTotal, 'total'); this.CheckPanelData(); if (isPreparing) { this.SwitchPanel('selection'); this.UpdatePanelStatus('loading', 'selection'); this.PrepareBulk(); } else if (isRunning) { this.SwitchPanel('process'); this.processor.PauseProcess(); // when loading, default start paused before resume. } else if (isFinished) { this.processor.StopProcess({ waiting: true }); if (initMedia.done > 0 || initCustom.done > 0) { this.SwitchPanel('finished'); } else { this.SwitchPanel('dashboard'); } //this.SwitchPanel('process'); // needs to run a process and get back stats another try. } else if (initMedia.in_queue > 0 || initCustom.in_queue > 0) { this.SwitchPanel('summary'); } else { this.processor.StopProcess({ waiting: true }); // don't go peeking in the queue. // this doesn't work since its' before the init Worker. this.SwitchPanel('dashboard'); } if (this.processor.isManualPaused) { var event = new CustomEvent('shortpixel.processor.paused', { detail : {paused: this.processor.isManualPaused }}); } // This var is defined in admin_scripts, localize. if ( typeof shortPixelScreen.panel !== 'undefined') { this.SwitchPanel(shortPixelScreen.panel); } } LoadPanels() { var elements = document.querySelectorAll('section.panel'); var self = this; elements.forEach(function (panel, index) { var panelName = panel.getAttribute('data-panel'); self.panels[panelName] = panel; }); } LoadActions() { var actions = document.querySelectorAll('[data-action]'); var self = this; actions.forEach(function (action, index) { var eventName = (action.getAttribute('data-event')) ? action.getAttribute('data-event') : 'click'; action.addEventListener(eventName, self.DoActionEvent.bind(self)); /* This is off, since I can't find any clue that children don't get triggered, but it does create double events when added. if (action.children.length > 0) { for(var i = 0; i < action.children.length; i++) { // action.children[i].addEventListener(eventName, self.DoActionEvent.bind(self)); } } */ }); } DoActionEvent() { var element = event.target; var action = element.getAttribute('data-action'); // Might be the child if (element.getAttribute('data-action') == null) { var element = element.parentElement; } if (element.disabled == true) // disabled button still register events, prevent going. { return false; } var actionName = element.getAttribute('data-action'); var isPanelAction = (actionName == 'open-panel'); if (isPanelAction) { var doPanel = element.getAttribute('data-panel'); this.SwitchPanel(doPanel); } else { if (typeof this[actionName] == 'function') { this[actionName].call(this,event); } } } UpdatePanelStatus(status, panelName) { if (typeof panelName !== 'undefined') var panel = this.panels[panelName]; else var panel = this.panels[this.currentPanel]; var currentStatus = panel.getAttribute('data-status'); panel.setAttribute('data-status', ''); setTimeout(function() { panel.setAttribute('data-status', status); }, 1000); var event = new CustomEvent('shortpixel.bulk.onUpdatePanelStatus', { detail : {status: status, oldStatus: currentStatus, panelName: panelName}}); window.dispatchEvent(event); } ToggleLoading(loading) { if (typeof loading == 'undefined' || loading == true) var loading = true; else var loading = false; var loader = document.getElementById('bulk-loading'); // This happens when out of quota. if (loader == null) return; if (loading) loader.setAttribute('data-status', 'loading'); else loader.setAttribute('data-status', 'not-loading'); } SwitchPanel(targetName) { console.debug('Switching Panel ' + targetName); this.ToggleLoading(false); if (! this.panels[targetName]) { console.error('Panel ' + targetName + ' does not exist?'); return; } else if (this.currentPanel == targetName) { return; // no switching needed. } // This detour is due to the issue that other plugins can attach prototype functions to array and this would return here. var panelKeys = Object.keys(this.panels); for (var i = 0; i < panelKeys.length; i++) { var panelName = panelKeys[i]; var panel = this.panels[panelName]; // Another prevention. if (typeof panel.classList === 'undefined') { continue; } panel.classList.remove('active'); panel.style.display = 'none'; }; var panel = this.panels[targetName]; panel.style.display = 'block'; // This should be the time of transition needed. // panel.classList.add('active'); // This non-delay makes the transition fade in properly. setTimeout(function() { panel.classList.add('active'); }, 0); var oldCurrentPanel = this.currentPanel; // for event this.currentPanel = targetName; if ( panel.getAttribute('data-loadPanel') !== null) { this[panel.getAttribute('data-loadPanel')].call(this); } var event = new CustomEvent('shortpixel.bulk.onSwitchPanel', { detail : {panelLoad: targetName, panelUnload: oldCurrentPanel}}); window.dispatchEvent(event); } CreateBulk() { console.log('Start Bulk'); var data = {screen_action: 'createBulk', callback: 'shortpixel.PrepareBulk'}; // data.mediaActive = (document.getElementById('media_checkbox').checked) ? true : false; data.customActive = (document.getElementById('custom_checkbox').checked) ? true : false; data.webpActive = (document.getElementById('webp_checkbox').checked) ? true : false; data.avifActive = (document.getElementById('avif_checkbox').checked) ? true : false; if (document.getElementById('thumbnails_checkbox') !== null) data.thumbsActive = (document.getElementById('thumbnails_checkbox').checked) ? true : false; this.UpdatePanelStatus('loading', 'selection'); // Prepare should happen after selecting what the optimize. window.addEventListener('shortpixel.PrepareBulk', this.PrepareBulk.bind(this), {'once': true} ); this.processor.AjaxRequest(data); } PrepareBulk(event) { //Remove pause if (typeof event == 'object') event.preventDefault(); // stop handler in checkResponse. this.processor.SetInterval(200); // do this faster. // CheckActive. Both Resume and Run call to processor process run. if (! this.processor.CheckActive()) { this.processor.ResumeProcess(); } else { this.processor.RunProcess(); } return false; // Run process.run process from now for prepare ( until prepare done? ) } QueueStatus(qStatus, data) { if (qStatus == 'PREPARING_DONE' || qStatus == 'PREPARING_RECOUNT') { console.log('Queue status: preparing done'); this.SwitchPanel('summary'); this.UpdatePanelStatus('loaded', 'selection'); this.processor.SetInterval(-1); // back to default. } if (qStatus == 'QUEUE_EMPTY') { // @todo Pre-release fix, not clean. Fix. var total = data.total.stats.total; if (typeof total == 'string') { var pattern = new RegExp("\\.|\\,", ''); total = total.replace(pattern, ''); } if (total > 0) { this.SwitchPanel('finished'); // if something actually was done. this.processor.StopProcess(); } else { this.SwitchPanel('dashboard'); // seems we are just at the begin. this.processor.StopProcess(); } // empty queue, no items, start. } } HandleImage(resultItem, type) { var result = resultItem.result; if ( this.processor.fStatus[resultItem.fileStatus] == 'FILE_DONE') { this.UpdateData('result', result); if (document.querySelector('.image-preview-section').classList.contains('hidden') ) { document.querySelector('.image-preview-section').classList.remove('hidden'); } this.HandleImageEffect(result.original, result.optimized); if (result.improvements.totalpercentage) { // Opt-Circle-Image is average of the file itself. var circle = document.querySelector('.opt-circle-image'); var total_circle = 289.027; if(result.improvements.totalpercentage >0 ) { total_circle = Math.round(total_circle-(total_circle*result.improvements.totalpercentage/100)); } for( var i = 0; i < circle.children.length; i++) { var child = circle.children[i]; if (child.classList.contains('path')) { child.style.strokeDashoffset = total_circle + 'px'; } else if (child.classList.contains('text')) { child.textContent = result.improvements.totalpercentage + '%'; } } this.AddAverageOptimization(result.improvements.totalpercentage); } return true; // This prevents flooding. } else if (typeof resultItem.preview !== 'undefined' && resultItem.preview != false) { /* Preloading doesn't solve it. var name = resultItem.preview.split(/[\\/]/).pop(); name = name.replace(/[^a-zA-Z0-9 ]/g, ''); var preLoader = document.getElementById('preloader'); if (preLoader.querySelector('[data-name="' + name + '"]') == null) { var el = document.createElement('span'); var img = document.createElement('img'); img.src = resultItem.preview; el.appendChild(img); el.dataset.name = name; preloader.appendChild(el) console.log('preloading URL with name', name, resultItem.preview, el); // Remove from DOM after a while to prevent DOM bloating. if (preloader.children.length >= 10) { preloader.children[0].remove(); } } */ } return false; } // Function to neatly slide the new / old images around. HandleImageEffect(originalSrc, optimizedSrc) { var preview = document.getElementById('preview-structure'); var offset = preview.offsetWidth; var placeHolder = preview.dataset.placeholder; if (preview.querySelector('.preview-image.old') !== null) { preview.querySelector('.preview-image.old').remove(); } var currentItem = preview.children[0]; var newItem = preview.children[1]; var cloneNode = newItem.cloneNode(true); if (originalSrc) { preview.querySelector('.new.preview-image .image.source img').src = originalSrc; } else { preview.querySelector('.new.preview-image .image.source').style.display = 'none'; } if (optimizedSrc) { preview.querySelector('.new.preview-image .image.result img').src = optimizedSrc; } else { preview.querySelector('.new.preview-image .image.result img').src = placeHolder; preview.querySelector('.new.preview-image .image.result img').classList.add('notempty'); } currentItem.style.marginLeft = '-' + offset + 'px'; setTimeout(function() { newItem.classList.remove('new'); newItem.classList.add('current'); currentItem.remove(); }, 1000); if (typeof cloneNode !== 'undefined') { cloneNode.querySelector('.image.source img').src = placeHolder; cloneNode.querySelector('.image.result img').src = placeHolder; preview.appendChild(cloneNode); } } AddAverageOptimization(num) { this.numOptimizations++; this.averageOptimization += num; var total = this.averageOptimization / this.numOptimizations; // There are circles on process and finished. var circles = document.querySelectorAll('.opt-circle-average'); circles.forEach(function (circle) { var total_circle = 289.027; if( total >0 ) { total_circle = Math.round(total_circle-(total_circle * total /100)); } for(var i = 0; i < circle.children.length; i++) { var child = circle.children[i]; if (child.classList.contains('path')) { child.style.strokeDashoffset = total_circle + 'px'; } else if (child.classList.contains('text')) { child.textContent = Math.round(total) + '%'; } } }); // circles; } DoSelection() // action to update response. { // @todo Check the future of this function, since checking this is now createBulk. var data = {screen_action: 'applyBulkSelection'}; // data.callback = 'shortpixel.applySelectionDone'; data.mediaActive = (document.getElementById('media_checkbox').checked) ? true : false; data.customActive = (document.getElementById('custom_checkbox').checked) ? true : false; data.webpActive = (document.getElementById('webp_checkbox').checked) ? true : false; data.avifActive = (document.getElementById('avif_checkbox').checked) ? true : false; window.addEventListener('shortpixel.applySelectionDone', function (e) { this.SwitchPanel('summary'); }.bind(this) , {'once': true} ); this.processor.AjaxRequest(data); } UpdateStats(stats, type) { this.UpdateData('stats', stats, type); } // dataName refers to domain of data i.e. stats, result. Those are mentioned in UI with data-stats-media="total" or data-result UpdateData(dataName, data, type) { console.log('updating Data :', dataName, data, type); if (typeof type == 'undefined') { var elements = document.querySelectorAll('[data-' + dataName + ']'); var attribute = 'data-' + dataName; } else { var elements = document.querySelectorAll('[data-' + dataName + '-' + type + ']'); var attribute = 'data-' + dataName + '-' + type; } if (elements) { elements.forEach(function (element, index) { var el = element.getAttribute(attribute); var presentation = false; if (element.hasAttribute('data-presentation')) presentation = element.getAttribute('data-presentation'); if (el == null) return; var index = el.indexOf('-'); if (index > -1) { var first = el.substr(0, index); var second = el.substr(index+1); if (typeof data[first] !== 'undefined' && typeof data[first][second] !== 'undefined') var value = data[first][second]; else var value = false; } else { if (typeof data[el] !== 'undefined') var value = data[el]; else var value = false; } if (presentation) { if (value !== false) { if (presentation == 'css.width.percentage') element.style.width = parseInt(value) + '%'; if (presentation == 'inputval') { element.value = value; } if (presentation == 'append') { element.innerHTML = element.innerHTML + value; } } } else { if (value !== false) { element.textContent = value; } } }); } } /** HandleError is used for item errors. The latter have a result object embedded and more information */ HandleItemError(result, type) { // console.error(result); var fatal = false; var cssClass = ''; var message = ''; var info = ''; if (typeof result.result !== 'undefined') // item error { if (result.result) { var item = result.result; var filename = (typeof item.filename !== 'undefined') ? item.filename : false; var message = item.message; var fatal = (item.is_done == true) ? true : false; if (filename) { message += ' (' + filename + ') '; } if (item.kblink) { var info = ' '; } } var error = this.processor.aStatusError[result.error]; if (error == 'NOQUOTA') { this.ToggleOverQuotaNotice(true); } } else // unknown. { var message = result.message + '(' + result.item_id + ')'; console.error('Error without item - ' + message); } if (fatal) cssClass += ' fatal'; var data = {message: '