5f86eb61 by Jeff Balicki

optimize

Signed-off-by: Jeff <jeff@gotenzing.com>
1 parent 0c383575
Showing 584 changed files with 4943 additions and 0 deletions
1 <IfModule mod_deflate.c>
2 # Force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/
3 <IfModule mod_setenvif.c>
4 <IfModule mod_headers.c>
5 SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
6 RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
7 </IfModule>
8 </IfModule>
1 9
10 # HTML, TXT, CSS, JavaScript, JSON, XML, HTC:
11 <IfModule filter_module>
12 FilterDeclare COMPRESS
13 FilterProvider COMPRESS DEFLATE resp=Content-Type $text/html
14 FilterProvider COMPRESS DEFLATE resp=Content-Type $text/css
15 FilterProvider COMPRESS DEFLATE resp=Content-Type $text/plain
16 FilterProvider COMPRESS DEFLATE resp=Content-Type $text/xml
17 FilterProvider COMPRESS DEFLATE resp=Content-Type $text/x-component
18 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/javascript
19 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/json
20 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xml
21 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/xhtml+xml
22 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/rss+xml
23 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/atom+xml
24 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/vnd.ms-fontobject
25 FilterProvider COMPRESS DEFLATE resp=Content-Type $image/svg+xml
26 FilterProvider COMPRESS DEFLATE resp=Content-Type $image/x-icon
27 FilterProvider COMPRESS DEFLATE resp=Content-Type $application/x-font-ttf
28 FilterProvider COMPRESS DEFLATE resp=Content-Type $font/opentype
29 FilterChain COMPRESS
30 FilterProtocol COMPRESS DEFLATE change=yes;byteranges=no
31 </IfModule>
32
33 <IfModule !mod_filter.c>
34 # Legacy versions of Apache
35 AddOutputFilterByType DEFLATE text/html text/plain text/css application/json
36 AddOutputFilterByType DEFLATE application/javascript
37 AddOutputFilterByType DEFLATE text/xml application/xml text/x-component
38 AddOutputFilterByType DEFLATE application/xhtml+xml application/rss+xml application/atom+xml
39 AddOutputFilterByType DEFLATE image/x-icon image/svg+xml application/vnd.ms-fontobject application/x-font-ttf font/opentype
40 </IfModule>
41
42 </IfModule>
2 # BEGIN WordPress 43 # BEGIN WordPress
3 # The directives (lines) between `BEGIN WordPress` and `END WordPress` are 44 # The directives (lines) between `BEGIN WordPress` and `END WordPress` are
4 # dynamically generated, and should only be modified via WordPress filters. 45 # dynamically generated, and should only be modified via WordPress filters.
......
1 import '../scss/app.scss';
2
3 /**
4 * Admin modules
5 */
6
7 const WP_Smush = WP_Smush || {};
8 window.WP_Smush = WP_Smush;
9
10 /**
11 * IE polyfill for includes.
12 *
13 * @since 3.1.0
14 * @param {string} search
15 * @param {number} start
16 * @return {boolean} Returns true if searchString appears as a substring of the result of converting this
17 * object to a String, at one or more positions that are
18 * greater than or equal to position; otherwise, returns false.
19 */
20 if ( ! String.prototype.includes ) {
21 String.prototype.includes = function( search, start ) {
22 if ( typeof start !== 'number' ) {
23 start = 0;
24 }
25
26 if ( start + search.length > this.length ) {
27 return false;
28 }
29 return this.indexOf( search, start ) !== -1;
30 };
31 }
32
33 require( './modules/helpers' );
34 require( './modules/admin' );
35 require( './modules/admin-common' );
36 require( './modules/bulk-smush' );
37 require( './modules/nextgen-bulk' );
38 require( './modules/background-process' );
39 require( './common/media-library-scanner' );
40 require( './modules/media-library-scanner-on-bulk-smush' );
41 require( './modules/media-library-scanner-on-dashboard' );
42 require( './modules/onboarding' );
43 require( './modules/directory-smush' );
44 require( './smush/cdn' );
45 require( './smush/webp' );
46 require( './smush/lazy-load' );
47 require( './modules/bulk-restore' );
48 require( './smush/settings' );
49
50 /**
51 * Notice scripts.
52 *
53 * Notices are used in the following functions:
54 *
55 * @used-by \Smush\Core\Modules\Smush::smush_updated()
56 * @used-by \Smush\Core\Integrations\S3::3_support_required_notice()
57 * @used-by \Smush\App\Abstract_Page::installation_notice()
58 *
59 * TODO: should this be moved out in a separate file like common.scss?
60 */
61 require( './modules/notice' );
1 /* global WP_Smush */
2 export const UpsellManger = ( () => {
3 return {
4 maybeShowCDNActivationNotice() {
5 if ( ! wp_smush_msgs.smush_cdn_activation_notice ) {
6 return;
7 }
8 WP_Smush.helpers.renderActivationCDNNotice( wp_smush_msgs.smush_cdn_activation_notice );
9 },
10 maybeShowCDNUpsellForPreSiteOnStart() {
11 const upsellCdn = document.querySelector( '.wp-smush-upsell-cdn' );
12 if ( upsellCdn ) {
13 upsellCdn.querySelector( 'p' ).innerHTML = wp_smush_msgs.processing_cdn_for_free;
14 upsellCdn.classList.remove( 'sui-hidden' );
15 }
16 },
17 maybeShowCDNUpsellForPreSiteOnCompleted() {
18 const upsellCdn = document.querySelector( '.wp-smush-upsell-cdn' );
19 if ( upsellCdn ) {
20 upsellCdn.querySelector( 'p' ).innerHTML = wp_smush_msgs.processed_cdn_for_free;
21 upsellCdn.classList.remove( 'sui-hidden' );
22 }
23 }
24 };
25 } )();
26 export const GlobalStats = ( () => {
27 const $ = document.querySelector.bind( document );
28 const summarySmush = $( '.sui-summary-smush-metabox' );
29 if ( ! summarySmush ) {
30 return {};
31 }
32 // Cache initial stats.
33 let boStats = window.wp_smushit_data.bo_stats;
34 let globalStats = {
35 count_images: 0,
36 count_total: 0,
37 count_resize: 0,
38 count_skipped: 0,
39 count_smushed: 0,
40 savings_bytes: 0,
41 savings_resize: 0,
42 size_after: 0,
43 size_before: 0,
44 savings_percent: 0,
45 percent_grade: 'sui-grade-dismissed',
46 percent_metric: 0,
47 percent_optimized: 0,
48 remaining_count: 0,
49 human_bytes: '',
50 savings_resize_human: '',
51 savings_conversion_human: '',
52 };
53
54 const imageScore = $( '#smush-image-score' );
55 const logBulk = $( '.smush-final-log .smush-bulk-errors' );
56 const bulkSmushCountContent = $( '#wp-smush-bulk-content' );
57 let allErrors = {};
58
59 const generateGlobalStatsFromSmushData = ( smushScriptData ) => {
60 window.wp_smushit_data = Object.assign( window.wp_smushit_data, smushScriptData || {} );
61 globalStats = Object.keys( globalStats ).reduce( function( newStats, key ) {
62 if ( key in window.wp_smushit_data ) {
63 newStats[ key ] = window.wp_smushit_data[ key ];
64 }
65 return newStats;
66 }, {} );
67 }
68
69 generateGlobalStatsFromSmushData( window.wp_smushit_data );
70
71 return {
72 isChangedStats( newBoStats ) {
73 const primaryKeys = [ 'total_items', 'processed_items', 'failed_items', 'is_cancelled', 'is_completed' ];
74 return primaryKeys.some( ( key ) => {
75 return newBoStats[ key ] !== boStats[ key ];
76 } );
77 },
78 setBoStats( newBoStats ) {
79 boStats = Object.assign( boStats, newBoStats || {} );
80 return this;
81 },
82 getBoStats() {
83 return boStats;
84 },
85 setGlobalStats( newGlobalStats ) {
86 globalStats = Object.assign( globalStats, newGlobalStats || {} );
87 return this;
88 },
89 getGlobalStats() {
90 return globalStats;
91 },
92 /**
93 * Circle progress bar.
94 */
95 renderScoreProgress() {
96 imageScore.className = imageScore.className.replace( /(^|\s)sui-grade-\S+/g, '' );
97 imageScore.classList.add( globalStats.percent_grade );
98 imageScore.dataset.score = globalStats.percent_optimized;
99 imageScore.querySelector( '.sui-circle-score-label' ).innerHTML = globalStats.percent_optimized;
100
101 imageScore
102 .querySelector( 'circle:last-child' )
103 .setAttribute( 'style', '--metric-array:' + ( 2.63893782902 * globalStats.percent_metric ) + ' ' + ( 263.893782902 - globalStats.percent_metric ) );
104 },
105 /**
106 * Summary detail - center meta box.
107 */
108 renderSummaryDetail() {
109 this.renderTotalStats();
110 this.renderResizedStats();
111 this.renderConversionSavings();
112 },
113 renderTotalStats() {
114 // Total savings.
115 summarySmush.querySelector( '.sui-summary-large.wp-smush-stats-human' ).innerHTML = globalStats.human_bytes;
116 // Update the savings percent.
117 summarySmush.querySelector( '.wp-smush-savings .wp-smush-stats-percent' ).innerHTML = globalStats.savings_percent;
118 // To total smushed images files.
119 summarySmush.querySelector( '.wp-smush-count-total .wp-smush-total-optimised' ).innerHTML = globalStats.count_images;
120 },
121 renderResizedStats() {
122 const resizeCountElement = summarySmush.querySelector( '.wp-smush-count-resize-total' );
123 if ( ! resizeCountElement ) {
124 return;
125 }
126 if ( globalStats.count_resize > 0 ) {
127 resizeCountElement.classList.remove( 'sui-hidden' );
128 } else {
129 resizeCountElement.classList.add( 'sui-hidden' );
130 }
131 resizeCountElement.querySelector( '.wp-smush-total-optimised' ).innerHTML = globalStats.count_resize;
132 // Image Resize Savings.
133 const resizeSavingsElement = summarySmush.querySelector( '.smush-resize-savings .wp-smush-stats' );
134 resizeSavingsElement && ( resizeSavingsElement.innerHTML = globalStats.savings_resize_human );
135 },
136 renderConversionSavings() {
137 // PNG2JPG Savings.
138 const conversionSavingsElement = summarySmush.querySelector( '.smush-conversion-savings .wp-smush-stats' );
139 if ( ! conversionSavingsElement ) {
140 return;
141 }
142 conversionSavingsElement.innerHTML = globalStats.savings_conversion_human;
143 if ( globalStats.savings_conversion > 0 ) {
144 conversionSavingsElement.parentElement.classList.remove( 'sui-hidden' );
145 } else {
146 conversionSavingsElement.parentElement.classList.add( 'sui-hidden' );
147 }
148 },
149 renderBoxSummary() {
150 // Circle core progress.
151 this.renderScoreProgress();
152 // Summary detail.
153 this.renderSummaryDetail();
154 },
155 setErrors( newErrors ) {
156 allErrors = newErrors || {};
157 },
158 getErrors() {
159 return allErrors;
160 },
161 renderErrors() {
162 if ( ! Object.keys( allErrors ).length || ! boStats.is_completed ) {
163 return;
164 }
165 const errors = [];
166 const errorKeys = Object.keys( allErrors );
167 // Cache error code to avoid double upsell notice.
168 let showAnimatedUpsell = false;
169 errorKeys.map( ( image_id, index ) => {
170 const upsellErrorCode = allErrors[ image_id ].error_code;
171 if ( index < 5 && 'animated' === upsellErrorCode ) {
172 showAnimatedUpsell = true;
173 }
174 errors.push( WP_Smush.helpers.prepareBulkSmushErrorRow(
175 allErrors[ image_id ].error_message,
176 allErrors[ image_id ].file_name,
177 allErrors[ image_id ].thumbnail,
178 image_id,
179 'media',
180 allErrors[ image_id ].error_code,
181 ) );
182 }
183 );
184 logBulk.innerHTML = errors.join( '' );
185 logBulk.parentElement.classList.remove( 'sui-hidden' );
186 logBulk.parentElement.style.display = null;
187 // Show view all.
188 if ( errorKeys.length > 1 ) {
189 $( '.smush-bulk-errors-actions' ).classList.remove( 'sui-hidden' );
190 }
191
192 // Show animated upsell CDN if user disabled CDN and found an animated error in first 5 errors.
193 if ( showAnimatedUpsell ) {
194 UpsellManger.maybeShowCDNActivationNotice();
195 }
196 },
197 resetAndHideBulkErrors() {
198 if ( ! logBulk ) {
199 return;
200 }
201 this.resetErrors();
202 logBulk.parentElement.classList.add( 'sui-hidden' );
203 logBulk.innerHTML = '';
204 },
205 resetErrors() {
206 allErrors = {};
207 },
208 renderStats() {
209 // Render Smush box summary.
210 this.renderBoxSummary();
211 // Render Errors.
212 this.renderErrors();
213 },
214 maybeUpdateBulkSmushCountContent( newContent ) {
215 if ( newContent && bulkSmushCountContent ) {
216 bulkSmushCountContent.innerHTML = newContent;
217 }
218 },
219 updateGlobalStatsFromSmushScriptData( smushScriptData ) {
220 this.maybeUpdateBulkSmushCountContent( smushScriptData?.content );
221 generateGlobalStatsFromSmushData( smushScriptData );
222 return this;
223 },
224 };
225 } )();
1 /* global WP_Smush */
2
3 /**
4 * Abstract Media Library Scanner.
5 *
6 */
7 import Fetcher from '../utils/fetcher';
8 import { scanProgressBar } from './progressbar';
9 import { GlobalStats } from './globalStats';
10 const { __ } = wp.i18n;
11 export default class MediaLibraryScanner {
12 constructor() {
13 this.autoSyncDuration = 1500;
14 this.progressTimeoutId = 0;
15 this.scanProgress = scanProgressBar( this.autoSyncDuration );
16 }
17
18 startScan( optimizeOnScanCompleted = false ) {
19 this.onStart();
20 return Fetcher.scanMediaLibrary.start( optimizeOnScanCompleted ).then( ( response ) => {
21 if ( ! response?.success ) {
22 this.onStartFailure( response );
23 return;
24 }
25 this.showProgressBar().autoSyncStatus();
26 } );
27 }
28
29 onStart() {
30 // Do nothing at the moment.
31 }
32
33 onStartFailure( response ) {
34 WP_Smush.helpers.showNotice( response, {
35 showdismiss: true,
36 autoclose: false,
37 } );
38 }
39
40 showProgressBar() {
41 this.onShowProgressBar();
42 this.scanProgress.reset().setOnCancelCallback( this.showStopScanningModal.bind( this ) ).open();
43 return this;
44 }
45
46 onShowProgressBar() {
47 // Do nothing at the moment.
48 }
49
50 showStopScanningModal() {
51 if ( ! window.SUI ) {
52 return;
53 }
54
55 this.onShowStopScanningModal();
56
57 window.SUI.openModal(
58 'smush-stop-scanning-dialog',
59 'wpbody-content',
60 undefined,
61 false
62 );
63 }
64
65 onShowStopScanningModal() {
66 this.registerCancelProcessEvent();
67 }
68
69 registerCancelProcessEvent() {
70 const stopScanButton = document.querySelector( '.smush-stop-scanning-dialog-button' );
71 if ( ! stopScanButton ) {
72 return;
73 }
74
75 stopScanButton.addEventListener( 'click', this.cancelProgress.bind( this ) );
76 }
77
78 closeStopScanningModal() {
79 if ( ! window.SUI ) {
80 return;
81 }
82 const stopScanningElement = document.querySelector( '#smush-stop-scanning-dialog' );
83 const isModalClosed = ( ! stopScanningElement ) || ! stopScanningElement.classList.contains( 'sui-content-fade-in' );
84 if ( isModalClosed ) {
85 return;
86 }
87 window.SUI.closeModal( 'smush-stop-scanning-dialog' );
88 }
89
90 closeProgressBar() {
91 this.onCloseProgressBar();
92 this.scanProgress.close();
93 }
94
95 onCloseProgressBar() {
96 // Do nothing at the moment.
97 }
98
99 updateProgress( stats ) {
100 const totalItems = this.getTotalItems( stats );
101 const processedItems = this.getProcessedItems( stats );
102
103 return this.scanProgress.update( processedItems, totalItems );
104 }
105
106 getProcessedItems( stats ) {
107 return stats?.processed_items || 0;
108 }
109
110 getTotalItems( stats ) {
111 return stats?.total_items || 0;
112 }
113
114 cancelProgress() {
115 this.scanProgress.setCancelButtonOnCancelling();
116 return Fetcher.scanMediaLibrary.cancel().then( ( response ) => {
117 if ( ! response?.success ) {
118 this.onCancelFailure( response );
119 return;
120 }
121 this.onCancelled( response.data );
122 } );
123 }
124
125 onCancelFailure( response ) {
126 WP_Smush.helpers.showNotice( response, {
127 showdismiss: true,
128 autoclose: false,
129 } );
130 this.scanProgress.resetCancelButtonOnFailure();
131 }
132
133 getErrorProgressMessage() {
134 return __( 'Unfortunately the scan hit an error due to limited resources on your site, we have adjusted the scan to use fewer resources the next time.', 'wp-smushit' );
135 }
136
137 onDead( stats ) {
138 this.clearProgressTimeout();
139 this.closeProgressBar();
140 this.closeStopScanningModal();
141 this.showRetryScanModal();
142 }
143
144 showRetryScanModal() {
145 const retryScanModalElement = document.getElementById( 'smush-retry-scan-notice' );
146 if ( ! window.SUI || ! retryScanModalElement ) {
147 return;
148 }
149
150 retryScanModalElement.querySelector('.smush-retry-scan-notice-button').onclick = (e) => {
151 window.SUI.closeModal( 'smush-retry-scan-notice' );
152 const recheckImagesBtn = document.querySelector( '.wp-smush-scan' );
153 if ( ! recheckImagesBtn ) {
154 return;
155 }
156
157 e.preventDefault();
158 recheckImagesBtn.click();
159 }
160
161 window.SUI.openModal(
162 'smush-retry-scan-notice',
163 'wpbody-content',
164 undefined,
165 false
166 );
167 }
168
169 onCompleted( stats ) {
170 this.onFinish( stats );
171 }
172
173 onCancelled( stats ) {
174 this.onFinish( stats );
175 }
176
177 onFinish( stats ) {
178 this.clearProgressTimeout();
179 const globalStats = stats?.global_stats;
180 this.updateGlobalStatsAndBulkContent( globalStats );
181 this.closeProgressBar();
182 this.closeStopScanningModal();
183 }
184
185 clearProgressTimeout() {
186 if ( this.progressTimeoutId ) {
187 clearTimeout( this.progressTimeoutId );
188 }
189 }
190
191 updateGlobalStatsAndBulkContent( globalStats ) {
192 if ( ! globalStats ) {
193 return;
194 }
195 GlobalStats.updateGlobalStatsFromSmushScriptData( globalStats );
196 GlobalStats.renderStats();
197 }
198
199 getStatus() {
200 return Fetcher.scanMediaLibrary.getScanStatus();
201 }
202
203 autoSyncStatus() {
204 const startTime = new Date().getTime();
205 this.getStatus().then( ( response ) => {
206 if ( ! response?.success ) {
207 return;
208 }
209 const stats = response.data;
210
211 if ( stats.is_dead ) {
212 this.onDead( response.data );
213 return;
214 }
215
216 this.beforeUpdateStatus( stats );
217
218 this.updateProgress( stats ).then( () => {
219 this.scanProgress.increaseDurationToHaveChangeOnProgress( new Date().getTime() - startTime );
220
221 const isCompleted = stats?.is_completed;
222 if ( isCompleted ) {
223 this.onCompleted( stats );
224 return;
225 }
226 const isCancelled = stats?.is_cancelled;
227 if ( isCancelled ) {
228 this.onCancelled( stats );
229 return;
230 }
231
232 this.progressTimeoutId = setTimeout( () => this.autoSyncStatus(), this.autoSyncDuration );
233 } );
234 } );
235 }
236
237 beforeUpdateStatus() {
238 // Do nothing at the moment.
239 }
240
241 setInnerText( element, newText ) {
242 if ( ! element ) {
243 return;
244 }
245 element.dataset.originalText = element.dataset.originalText || element.innerText.trim();
246 element.innerText = newText;
247 }
248
249 revertInnerText( element ) {
250 if ( ! element || ! element.dataset.originalText ) {
251 return;
252 }
253 element.innerText = element.dataset.originalText.trim();
254 }
255
256 hideAnElement( element ) {
257 if ( element ) {
258 element.classList.add( 'sui-hidden' );
259 }
260 }
261
262 showAnElement( element ) {
263 if ( element ) {
264 element.classList.remove( 'sui-hidden' );
265 }
266 }
267 }
1 /* global WP_Smush */
2
3 /**
4 * SmushProgressBar
5 * TODO: Update progressbar for free version.
6 *
7 * @param autoSyncDuration
8 */
9 export const scanProgressBar = ( autoSyncDuration ) => {
10 const { __, _n } = wp.i18n;
11 const scanProgressBar = document.querySelector( '.wp-smush-scan-progress-bar-wrapper' );
12 const percentElement = scanProgressBar.querySelector( '.wp-smush-progress-percent' );
13 const progressElement = scanProgressBar.querySelector( '.wp-smush-progress-inner' );
14 const remainingTimeElement = scanProgressBar.querySelector( '.wp-smush-remaining-time' );
15 const cancelBtn = scanProgressBar.querySelector( '.wp-smush-cancel-scan-progress-btn' );
16 const holdOnNoticeElement = scanProgressBar.querySelector( '.wp-smush-scan-hold-on-notice' );
17 let onCancelCallback = () => {};
18 let intervalProgressAnimation = 0;
19 // It should be smaller than autoSyncDuration.
20 const progressTransitionDuration = autoSyncDuration - 300;//1200
21 scanProgressBar.style.setProperty( '--progress-transition-duration', progressTransitionDuration / 1000 + 's' );
22
23 let prevProcessedItems = window.wp_smushit_data?.media_library_scan?.processed_items || 0;
24 const cacheProcessTimePerItem = [];
25 let durationToHaveChangeOnProgress = autoSyncDuration;
26 let timeLimitToShowNotice = autoSyncDuration * 10;// 15s.
27 return {
28 update( processedItems, totalItems ) {
29 this.updateRemainingTime( processedItems, totalItems );
30
31 let width = ( totalItems && Math.floor( processedItems / totalItems * 100 ) ) || 0;
32 width = Math.min( width, 100 );
33
34 let currentWidth = progressElement.style.width;
35 currentWidth = ( currentWidth && currentWidth.replace( '%', '' ) ) || 0;
36 progressElement.style.width = width + '%';
37
38 return this.animateProgressBar( currentWidth, width );
39 },
40 animateProgressBar( currentWidth, width ) {
41 if ( intervalProgressAnimation ) {
42 clearInterval( intervalProgressAnimation );
43 }
44 return new Promise( ( resolve ) => {
45 const delayTime = progressTransitionDuration / ( width - currentWidth );
46 intervalProgressAnimation = setInterval( () => {
47 // Progress bar label.
48 percentElement.innerHTML = currentWidth + '%';
49 currentWidth++;
50 if ( currentWidth > width ) {
51 resolve();
52 clearInterval( intervalProgressAnimation );
53 }
54 }, delayTime );
55 } );
56 },
57 updateRemainingTime( processedItems, totalItems ) {
58 if ( ! remainingTimeElement ) {
59 return;
60 }
61 const processTimePerItem = this.calcProcessTimePerItem( processedItems ) || 500;
62 const remainingTime = processTimePerItem * ( totalItems - processedItems );
63 remainingTimeElement.innerText = this.formatTime( remainingTime );
64 },
65 calcProcessTimePerItem( processedItems ) {
66 if ( ! processedItems ) {
67 return;
68 }
69 prevProcessedItems = prevProcessedItems <= processedItems ? prevProcessedItems : 0;
70 if ( prevProcessedItems != processedItems ) {
71 const processTimePerItem = Math.floor( durationToHaveChangeOnProgress / ( processedItems - prevProcessedItems ) );
72
73 prevProcessedItems = processedItems;
74 cacheProcessTimePerItem.push( processTimePerItem );
75 this.resetDurationToHaveChangeOnProgress();
76 } else {
77 this.increaseDurationToHaveChangeOnProgress( autoSyncDuration );
78 }
79
80 if ( ! cacheProcessTimePerItem.length ) {
81 return;
82 }
83
84 return cacheProcessTimePerItem.reduce(
85 ( accumulator, processTime ) => accumulator + processTime, 0
86 ) / cacheProcessTimePerItem.length;
87 },
88 increaseDurationToHaveChangeOnProgress( increaseTime ) {
89 durationToHaveChangeOnProgress += increaseTime;
90 if ( durationToHaveChangeOnProgress > timeLimitToShowNotice ) {
91 this.showHoldOnNotice();
92 }
93 },
94 showHoldOnNotice() {
95 holdOnNoticeElement.classList.remove( 'sui-hidden' );
96 timeLimitToShowNotice = 100000000;
97 },
98 resetHoldOnNoticeVisibility() {
99 holdOnNoticeElement.classList.add( 'sui-hidden' );
100 },
101 resetDurationToHaveChangeOnProgress() {
102 durationToHaveChangeOnProgress = autoSyncDuration;
103 },
104 formatTime( totalMilliSeconds ) {
105 const totalSeconds = Math.floor( ( totalMilliSeconds + progressTransitionDuration ) / 1000 );
106 const seconds = totalSeconds % 60;
107 const minutes = Math.floor( totalSeconds / 60 );
108
109 let timeString = '';
110 if ( minutes ) {
111 timeString += minutes + ' ' + _n( 'minute', 'minutes', minutes, 'wp-smushit' );
112 }
113
114 timeString += ' ' + seconds + ' ' + _n( 'second', 'seconds', seconds, 'wp-smushit' );
115
116 return timeString.trim();
117 },
118 reset() {
119 progressElement.style.width = '0%';
120 percentElement.innerHTML = '0%';
121
122 this.resetCancelButton();
123 this.resetHoldOnNoticeVisibility();
124 return this;
125 },
126 open() {
127 cancelBtn.onclick = onCancelCallback;
128 scanProgressBar.classList.remove( 'sui-hidden' );
129 },
130 close() {
131 scanProgressBar.classList.add( 'sui-hidden' );
132 this.reset();
133 },
134 setOnCancelCallback( callBack ) {
135 if ( 'function' !== typeof callBack ) {
136 return;
137 }
138 onCancelCallback = callBack;
139 return this;
140 },
141 setCancelButtonLabel( textContent ) {
142 cancelBtn.textContent = textContent;
143 return this;
144 },
145 setCancelButtonOnCancelling() {
146 this.setCancelButtonLabel( wp_smush_msgs.cancelling );
147 this.setOnCancelCallback( () => false );
148 cancelBtn.setAttribute( 'disabled', true );
149 },
150 resetCancelButton() {
151 this.setOnCancelCallback( () => {} );
152 this.resetCancelButtonLabel();
153 cancelBtn.removeAttribute( 'disabled' );
154 },
155 resetCancelButtonLabel() {
156 this.setCancelButtonLabel( __( 'Cancel Scan', 'wp-smushit' ) );
157 },
158 resetCancelButtonOnFailure() {
159 this.resetCancelButtonLabel();
160 cancelBtn.removeAttribute( 'disabled' );
161 }
162 };
163 };
164
165 const SmushProgressBar = () => {
166 'use strict';
167 const progressBar = document.querySelector( '.wp-smush-bulk-progress-bar-wrapper' );
168 if ( ! progressBar ) {
169 return {
170 isEmptyObject: true,
171 };
172 }
173 const cancelBtn = progressBar.querySelector( '.wp-smush-cancel-btn' );
174 const bulkSmushDescription = document.querySelector( '.wp-smush-bulk-wrapper' );
175 const bulkRunningNotice = progressBar.querySelector( '#wp-smush-running-notice' );
176 const bulkSmushAllDone = document.querySelector( '.wp-smush-all-done' );
177 let isStateHidden = false;
178 let onCancelCallback = () => {};
179
180 return {
181 /**
182 * Update progress bar.
183 *
184 * @param {number} processedItems
185 * @param {number} totalItems
186 */
187 update( processedItems, totalItems ) {
188 let width = totalItems && Math.floor( processedItems / totalItems * 100 ) || 0;
189 width = Math.min( width, 100 );
190
191 // Progress bar label.
192 progressBar.querySelector( '.wp-smush-images-percent' ).innerHTML = width + '%';
193 // Progress bar.
194 progressBar.querySelector( '.wp-smush-progress-inner' ).style.width = width + '%';
195
196 // Update processed/total.
197 const processStateStats = progressBar.querySelector( '.sui-progress-state-text' );
198 processStateStats.firstElementChild.innerHTML = processedItems;
199 processStateStats.lastElementChild.innerHTML = totalItems;
200
201 return this;
202 },
203 close() {
204 progressBar.classList.add( 'sui-hidden' );
205 this.setCancelButtonLabel( window.wp_smush_msgs.cancel )
206 .setOnCancelCallback( () => {} )
207 .update( 0, 0 );
208 this.resetOriginalNotice();
209 return this;
210 },
211 show() {
212 // Show progress bar.
213 cancelBtn.onclick = onCancelCallback;
214 progressBar.classList.remove( 'sui-hidden' );
215 this.hideBulkSmushDescription();
216 this.hideBulkSmushAllDone();
217 this.hideRecheckImagesNotice();
218 },
219 setCancelButtonLabel( textContent ) {
220 cancelBtn.textContent = textContent;
221 return this;
222 },
223 showBulkSmushDescription() {
224 bulkSmushDescription.classList.remove( 'sui-hidden' );
225 },
226 hideBulkSmushDescription() {
227 bulkSmushDescription.classList.add( 'sui-hidden' );
228 },
229 showBulkSmushAllDone() {
230 bulkSmushAllDone.classList.remove( 'sui-hidden' );
231 },
232 hideBulkSmushAllDone() {
233 bulkSmushAllDone.classList.add( 'sui-hidden' );
234 },
235 hideState() {
236 if ( isStateHidden ) {
237 return this;
238 }
239 isStateHidden = true;
240 progressBar.querySelector( '.sui-progress-state' ).classList.add( 'sui-hidden' );
241 return this;
242 },
243 showState() {
244 if ( ! isStateHidden ) {
245 return this;
246 }
247 isStateHidden = false;
248 progressBar.querySelector( '.sui-progress-state' ).classList.remove( 'sui-hidden' );
249 return this;
250 },
251 setNotice( inProcessNotice ) {
252 const progressMessage = bulkRunningNotice.querySelector( '.sui-notice-message p' );
253 this.cacheOriginalNotice( progressMessage );
254 progressMessage.innerHTML = inProcessNotice;
255 return this;
256 },
257 cacheOriginalNotice( progressMessage ) {
258 if ( bulkRunningNotice.dataset.progressMessage ) {
259 return;
260 }
261 bulkRunningNotice.dataset.progressMessage = progressMessage.innerHTML;
262 },
263 resetOriginalNotice() {
264 if ( ! bulkRunningNotice.dataset.progressMessage ) {
265 return;
266 }
267 const progressMessage = bulkRunningNotice.querySelector( '.sui-notice-message p' );
268 progressMessage.innerHTML = bulkRunningNotice.dataset.progressMessage;
269 },
270 hideBulkProcessingNotice() {
271 bulkRunningNotice.classList.add( 'sui-hidden' );
272 return this;
273 },
274 showBulkProcessingNotice() {
275 bulkRunningNotice.classList.remove( 'sui-hidden' );
276 return this;
277 },
278 setCountUnitText( unitText ) {
279 const progressUnit = progressBar.querySelector( '.sui-progress-state-unit' );
280 progressUnit.innerHTML = unitText;
281 },
282 setOnCancelCallback( callBack ) {
283 if ( 'function' !== typeof callBack ) {
284 return;
285 }
286 onCancelCallback = callBack;
287 return this;
288 },
289 disableExceedLimitMode() {
290 progressBar.classList.remove( 'wp-smush-exceed-limit' );
291 progressBar.querySelector( '#bulk-smush-resume-button' ).classList.add( 'sui-hidden' );
292 },
293 hideRecheckImagesNotice() {
294 const recheckImagesNoticeElement = document.querySelector( '.wp-smush-recheck-images-notice-box' );
295 if ( recheckImagesNoticeElement ) {
296 recheckImagesNoticeElement.classList.add( 'sui-hidden' );
297 }
298 }
299
300 };
301 };
302 export default new SmushProgressBar();
1 import lazySizes from 'lazysizes';
2 import 'lazysizes/plugins/native-loading/ls.native-loading';
3
4 lazySizes.cfg.nativeLoading = {
5 setLoadingAttribute: true,
6 disableListeners: {
7 scroll: true,
8 },
9 };
10
11 lazySizes.init();
1 import lazySizes from 'lazysizes';
2
3 lazySizes.init();
1 import '../../scss/resize-detection.scss';
2
3 /**
4 * Image resize detection (IRD).
5 *
6 * Show all wrongly scaled images with a highlighted border and resize box.
7 *
8 * Made in pure JS.
9 * DO NOT ADD JQUERY SUPPORT!!!
10 *
11 * @since 2.9
12 */
13 ( function() {
14 'use strict';
15
16 const SmushIRS = {
17 bar: document.getElementById( 'smush-image-bar' ),
18 toggle: document.getElementById( 'smush-image-bar-toggle' ),
19 images: {
20 bigger: [],
21 smaller: [],
22 },
23 strings: window.wp_smush_resize_vars,
24
25 /**
26 * Init scripts.
27 */
28 init() {
29 /**
30 * Make sure these are set, before we proceed.
31 */
32 if ( ! this.bar ) {
33 this.bar = document.getElementById( 'smush-image-bar' );
34 }
35 if ( ! this.toggle ) {
36 this.toggle = document.getElementById(
37 'smush-image-bar-toggle'
38 );
39 }
40
41 this.process();
42
43 // Register the event handler after everything is done.
44 this.toggle.addEventListener(
45 'click',
46 this.handleToggleClick.bind( this )
47 );
48 },
49
50 /**
51 * Do image processing.
52 */
53 process() {
54 const icon = this.toggle.querySelector( 'i' );
55
56 icon.classList.add( 'sui-icon-loader' );
57 icon.classList.remove( 'sui-icon-info' );
58
59 this.detectImages();
60
61 if ( ! this.images.bigger.length && ! this.images.smaller.length ) {
62 this.toggle.classList.add( 'smush-toggle-success' );
63 document.getElementById(
64 'smush-image-bar-notice'
65 ).style.display = 'block';
66 document.getElementById(
67 'smush-image-bar-notice-desc'
68 ).style.display = 'none';
69 } else {
70 this.toggle.classList.remove( 'smush-toggle-success' );
71 document.getElementById(
72 'smush-image-bar-notice'
73 ).style.display = 'none';
74 document.getElementById(
75 'smush-image-bar-notice-desc'
76 ).style.display = 'block';
77 this.generateMarkup( 'bigger' );
78 this.generateMarkup( 'smaller' );
79 }
80
81 this.toggleDivs();
82
83 icon.classList.remove( 'sui-icon-loader' );
84 icon.classList.add( 'sui-icon-info' );
85 },
86
87 /**
88 * Various checks to see if the image should be processed.
89 *
90 * @param {Object} image
91 * @return {boolean} Should skip image or not.
92 */
93 shouldSkipImage( image ) {
94 // Skip avatars.
95 if ( image.classList.contains( 'avatar' ) ) {
96 return true;
97 }
98
99 // Skip images from Smush CDN with auto-resize feature.
100 if (
101 'string' === typeof image.getAttribute( 'no-resize-detection' )
102 ) {
103 return true;
104 }
105
106 // Skip 1x1px images.
107 if (
108 image.clientWidth === image.clientHeight &&
109 1 === image.clientWidth
110 ) {
111 return true;
112 }
113
114 // Skip 1x1px placeholders.
115 if (
116 image.naturalWidth === image.naturalHeight &&
117 1 === image.naturalWidth
118 ) {
119 return true;
120 }
121
122 // If width attribute is not set, do not continue.
123 return null === image.clientWidth || null === image.clientHeight;
124 },
125
126 /**
127 * Get tooltip text.
128 *
129 * @param {Object} props
130 * @return {string} Tooltip.
131 */
132 getTooltipText( props ) {
133 let tooltipText = '';
134
135 if ( props.bigger_width || props.bigger_height ) {
136 /** @param {string} strings.large_image */
137 tooltipText = this.strings.large_image;
138 } else if ( props.smaller_width || props.smaller_height ) {
139 /** @param {string} strings.small_image */
140 tooltipText = this.strings.small_image;
141 }
142
143 return tooltipText
144 .replace( 'width', props.real_width )
145 .replace( 'height', props.real_height );
146 },
147
148 /**
149 * Generate markup.
150 *
151 * @param {string} type Accepts: 'bigger' or 'smaller'.
152 */
153 generateMarkup( type ) {
154 this.images[ type ].forEach( ( image, index ) => {
155 const item = document.createElement( 'div' ),
156 tooltip = this.getTooltipText( image.props );
157
158 item.setAttribute(
159 'class',
160 'smush-resize-box smush-tooltip smush-tooltip-constrained'
161 );
162 item.setAttribute( 'data-tooltip', tooltip );
163 item.setAttribute( 'data-image', image.class );
164 item.addEventListener( 'click', ( e ) =>
165 this.highlightImage( e )
166 );
167
168 item.innerHTML = `
169 <div class="smush-image-info">
170 <span>${ index + 1 }</span>
171 <span class="smush-tag">${ image.props.computed_width } x ${ image.props.computed_height }px</span>
172 <i class="smush-front-icons smush-front-icon-arrows-in" aria-hidden="true">&nbsp;</i>
173 <span class="smush-tag smush-tag-success">${ image.props.real_width } × ${ image.props.real_height }px</span>
174 </div>
175 <div class="smush-image-description">${ tooltip }</div>
176 `;
177
178 document
179 .getElementById( 'smush-image-bar-items-' + type )
180 .appendChild( item );
181 } );
182 },
183
184 /**
185 * Show/hide sections based on images.
186 */
187 toggleDivs() {
188 const types = [ 'bigger', 'smaller' ];
189 types.forEach( ( type ) => {
190 const div = document.getElementById(
191 'smush-image-bar-items-' + type
192 );
193 if ( 0 === this.images[ type ].length ) {
194 div.style.display = 'none';
195 } else {
196 div.style.display = 'block';
197 }
198 } );
199 },
200
201 /**
202 * Scroll the selected image into view and highlight it.
203 *
204 * @param {Object} e
205 */
206 highlightImage( e ) {
207 this.removeSelection();
208
209 const el = document.getElementsByClassName(
210 e.currentTarget.dataset.image
211 );
212 if ( 'undefined' !== typeof el[ 0 ] ) {
213 // Display description box.
214 e.currentTarget.classList.toggle( 'show-description' );
215
216 // Scroll and flash image.
217 el[ 0 ].scrollIntoView( {
218 behavior: 'smooth',
219 block: 'center',
220 inline: 'nearest',
221 } );
222 el[ 0 ].style.opacity = '0.5';
223 setTimeout( () => {
224 el[ 0 ].style.opacity = '1';
225 }, 1000 );
226 }
227 },
228
229 /**
230 * Handle click on the toggle item.
231 */
232 handleToggleClick() {
233 this.bar.classList.toggle( 'closed' );
234 this.toggle.classList.toggle( 'closed' );
235 this.removeSelection();
236 },
237
238 /**
239 * Remove selected items.
240 */
241 removeSelection() {
242 const items = document.getElementsByClassName( 'show-description' );
243 if ( items.length > 0 ) {
244 Array.from( items ).forEach( ( div ) =>
245 div.classList.remove( 'show-description' )
246 );
247 }
248 },
249
250 /**
251 * Function to highlight all scaled images.
252 *
253 * Add yellow border and then show one small box to
254 * resize the images as per the required size, on fly.
255 */
256 detectImages() {
257 const images = document.getElementsByTagName( 'img' );
258 for ( const image of images ) {
259 if ( this.shouldSkipImage( image ) ) {
260 continue;
261 }
262
263 // Get defined width and height.
264 const props = {
265 real_width: image.clientWidth,
266 real_height: image.clientHeight,
267 computed_width: image.naturalWidth,
268 computed_height: image.naturalHeight,
269 bigger_width: image.clientWidth * 1.5 < image.naturalWidth,
270 bigger_height:
271 image.clientHeight * 1.5 < image.naturalHeight,
272 smaller_width: image.clientWidth > image.naturalWidth,
273 smaller_height: image.clientHeight > image.naturalHeight,
274 };
275
276 // In case image is in correct size, do not continue.
277 if (
278 ! props.bigger_width &&
279 ! props.bigger_height &&
280 ! props.smaller_width &&
281 ! props.smaller_height
282 ) {
283 continue;
284 }
285
286 const imgType =
287 props.bigger_width || props.bigger_height
288 ? 'bigger'
289 : 'smaller',
290 imageClass =
291 'smush-image-' + ( this.images[ imgType ].length + 1 );
292
293 // Fill the images arrays.
294 this.images[ imgType ].push( {
295 src: image,
296 props,
297 class: imageClass,
298 } );
299
300 /**
301 * Add class to original image.
302 * Can't add two classes in single add(), because no support in IE11.
303 * image.classList.add('smush-detected-img', imageClass);
304 */
305 image.classList.add( 'smush-detected-img' );
306 image.classList.add( imageClass );
307 }
308 }, // End detectImages()
309
310 /**
311 * Allows refreshing the list. A good way is to refresh on lazyload actions.
312 *
313 * @since 3.6.0
314 */
315 refresh() {
316 // Clear out classes on DOM.
317 for ( let id in this.images.bigger ) {
318 if ( this.images.bigger.hasOwnProperty( id ) ) {
319 this.images.bigger[ id ].src.classList.remove(
320 'smush-detected-img'
321 );
322 this.images.bigger[ id ].src.classList.remove(
323 'smush-image-' + ++id
324 );
325 }
326 }
327
328 for ( let id in this.images.smaller ) {
329 if ( this.images.smaller.hasOwnProperty( id ) ) {
330 this.images.smaller[ id ].src.classList.remove(
331 'smush-detected-img'
332 );
333 this.images.smaller[ id ].src.classList.remove(
334 'smush-image-' + ++id
335 );
336 }
337 }
338
339 this.images = {
340 bigger: [],
341 smaller: [],
342 };
343
344 // This might be overkill - there will probably never be a situation when there are less images than on
345 // initial page load.
346 const elements = document.getElementsByClassName(
347 'smush-resize-box'
348 );
349 while ( elements.length > 0 ) {
350 elements[ 0 ].remove();
351 }
352
353 this.process();
354 },
355 }; // End WP_Smush_IRS
356
357 /**
358 * After page load, initialize toggle event.
359 */
360 window.addEventListener( 'DOMContentLoaded', () => SmushIRS.init() );
361 window.addEventListener( 'lazyloaded', () => SmushIRS.refresh() );
362 }() );
1 import '../scss/common.scss';
2
3 /* global ajaxurl */
4
5 document.addEventListener('DOMContentLoaded', function () {
6 const dismissNoticeButton = document.querySelectorAll(
7 '.smush-dismissible-notice .smush-dismiss-notice-button'
8 );
9 dismissNoticeButton.forEach((button) => {
10 button.addEventListener('click', handleDismissNotice);
11 });
12
13 function handleDismissNotice(event) {
14 event.preventDefault();
15
16 const button = event.target;
17 const notice = button.closest('.smush-dismissible-notice');
18 const key = notice.getAttribute('data-key');
19
20 dismissNotice( key, notice );
21 }
22
23 function dismissNotice( key, notice ) {
24 const xhr = new XMLHttpRequest();
25 xhr.open(
26 'POST',
27 ajaxurl + '?action=smush_dismiss_notice&key=' + key + '&_ajax_nonce=' + smush_global.nonce,
28 true
29 );
30 xhr.onload = () => {
31 if (notice) {
32 notice.querySelector('button.notice-dismiss').dispatchEvent(new MouseEvent('click', {
33 view: window,
34 bubbles: true,
35 cancelable: true
36 }));
37 }
38 };
39 xhr.send();
40 }
41
42
43 // Show header notices.
44 const handleHeaderNotice = () => {
45 const headerNotice = document.querySelector('.wp-smush-dismissible-header-notice');
46 if ( ! headerNotice || ! headerNotice.id ) {
47 return;
48 }
49
50 const { dismissKey, message } = headerNotice.dataset;
51 if ( ! dismissKey || ! message ) {
52 return;
53 }
54
55 headerNotice.onclick = (e) => {
56 const classList = e.target.classList;
57 const isDismissButton = classList && ( classList.contains('sui-icon-check') || classList.contains('sui-button-icon') );
58 if ( ! isDismissButton ) {
59 return;
60 }
61 dismissNotice( dismissKey );
62 }
63
64 const noticeOptions = {
65 type: 'warning',
66 icon: 'info',
67 dismiss: {
68 show: true,
69 label: wp_smush_msgs.noticeDismiss,
70 tooltip: wp_smush_msgs.noticeDismissTooltip,
71 },
72 };
73
74 window.SUI.openNotice(
75 headerNotice.id,
76 message,
77 noticeOptions
78 );
79 }
80
81 handleHeaderNotice();
82
83 });
1 /* global wp_smush_mixpanel */
2
3 import mixpanel from "mixpanel-browser";
4
5 export default class MixPanel {
6 constructor() {
7 this.mixpanelInstance = mixpanel.init(wp_smush_mixpanel.token, {
8 opt_out_tracking_by_default: !wp_smush_mixpanel.opt_in,
9 loaded: (mixpanel) => {
10 mixpanel.identify(wp_smush_mixpanel.unique_id);
11 mixpanel.register(wp_smush_mixpanel.super_properties);
12
13 const trackingActiveInSmushSettings = !!wp_smush_mixpanel.opt_in;
14 if (mixpanel.has_opted_in_tracking() !== trackingActiveInSmushSettings) {
15 // The status cached by MixPanel in the local storage is different from the settings. Clear the cache.
16 mixpanel.clear_opt_in_out_tracking();
17 }
18 }
19 }, 'smush');
20 }
21
22 track(event, properties = {}) {
23 this.mixpanelInstance.track(event, properties);
24 }
25
26 trackBulkSmushCompleted( globalStats ) {
27 const {
28 savings_bytes,
29 count_images,
30 percent_optimized,
31 savings_percent,
32 count_resize,
33 savings_resize
34 } = globalStats;
35 this.track('Bulk Smush Completed', {
36 'Total Savings': this.convertToMegabytes( savings_bytes ),
37 'Total Images': count_images,
38 'Media Optimization Percentage': parseFloat( percent_optimized ),
39 'Percentage of Savings': parseFloat( savings_percent ),
40 'Images Resized': count_resize,
41 'Resize Savings': this.convertToMegabytes( savings_resize )
42 });
43 }
44
45 trackBulkSmushCancel() {
46 this.track('Bulk Smush Cancelled');
47 }
48
49 convertToMegabytes( sizeInBytes ) {
50 const unitMB = Math.pow( 1024, 2 );
51 const sizeInMegabytes = sizeInBytes/unitMB;
52 return sizeInMegabytes && parseFloat(sizeInMegabytes.toFixed(2)) || 0;
53 }
54 }
1 jQuery(function ($) {
2 'use strict';
3
4 /**
5 * Handle the Smush Stats link click
6 */
7 $('body').on('click', 'a.smush-stats-details', function (e) {
8 //If disabled
9 if ($(this).prop('disabled')) {
10 return false;
11 }
12
13 // prevent the default action
14 e.preventDefault();
15 //Replace the `+` with a `-`
16 const slide_symbol = $(this).find('.stats-toggle');
17 $(this).parents().eq(1).find('.smush-stats-wrapper').slideToggle();
18 slide_symbol.text(slide_symbol.text() == '+' ? '-' : '+');
19 });
20 });
...\ No newline at end of file ...\ No newline at end of file
1 /* global WP_Smush */
2 /* global ajaxurl */
3 /* global _ */
4
5 import MixPanel from "../mixpanel";
6
7 /**
8 * Bulk restore JavaScript code.
9 *
10 * @since 3.2.2
11 */
12 (function () {
13 'use strict';
14
15 /**
16 * Bulk restore modal.
17 *
18 * @since 3.2.2
19 */
20 WP_Smush.restore = {
21 modal: document.getElementById('smush-restore-images-dialog'),
22 contentContainer: document.getElementById('smush-bulk-restore-content'),
23 settings: {
24 slide: 'start', // start, progress or finish.
25 success: 0,
26 errors: [],
27 },
28 items: [], // total items, 1 item = 1 step.
29 success: [], // successful items restored.
30 errors: [], // failed items.
31 currentStep: 0,
32 totalSteps: 0,
33
34 /**
35 * Init module.
36 */
37 init() {
38 if (!this.modal) {
39 return;
40 }
41
42 this.settings = {
43 slide: 'start',
44 success: 0,
45 errors: [],
46 };
47
48 this.mixPanel = new MixPanel();
49
50 this.resetModalWidth();
51 this.renderTemplate();
52
53 // Show the modal.
54
55 window.SUI.openModal(
56 'smush-restore-images-dialog',
57 'wpbody-content',
58 undefined,
59 false
60 );
61 },
62
63 /**
64 * Update the template, register new listeners.
65 */
66 renderTemplate() {
67 const template = WP_Smush.onboarding.template('smush-bulk-restore');
68 const content = template(this.settings);
69
70 if (content) {
71 this.contentContainer.innerHTML = content;
72 }
73
74 this.bindSubmit();
75 },
76
77 /**
78 * Reset modal width.
79 *
80 * @since 3.6.0
81 */
82 resetModalWidth() {
83 this.modal.style.maxWidth = '460px';
84 this.modal.querySelector('.sui-box').style.maxWidth = '460px';
85 },
86
87 /**
88 * Catch "Finish setup wizard" button click.
89 */
90 bindSubmit() {
91 const confirmButton = this.modal.querySelector(
92 'button[id="smush-bulk-restore-button"]'
93 );
94 const self = this;
95
96 if (confirmButton) {
97 confirmButton.addEventListener('click', function (e) {
98 e.preventDefault();
99 self.resetModalWidth();
100
101 self.settings = { slide: 'progress' };
102 self.errors = [];
103
104 self.renderTemplate();
105 self.initScan();
106
107 self.mixPanel.track('Bulk Restore Triggered');
108 });
109 }
110 },
111
112 /**
113 * Cancel the bulk restore.
114 */
115 cancel() {
116 if (
117 'start' === this.settings.slide ||
118 'finish' === this.settings.slide
119 ) {
120 // Hide the modal.
121 window.SUI.closeModal();
122 } else {
123 this.updateProgressBar(true);
124 window.location.reload();
125 }
126 },
127
128 /**
129 * Update progress bar during directory smush.
130 *
131 * @param {boolean} cancel Cancel status.
132 */
133 updateProgressBar(cancel = false) {
134 let progress = 0;
135 if (0 < this.currentStep) {
136 progress = Math.min(
137 Math.round((this.currentStep * 100) / this.totalSteps),
138 99
139 );
140 }
141
142 if (progress > 100) {
143 progress = 100;
144 }
145
146 // Update progress bar
147 this.modal.querySelector('.sui-progress-text span').innerHTML =
148 progress + '%';
149 this.modal.querySelector('.sui-progress-bar span').style.width =
150 progress + '%';
151
152 const statusDiv = this.modal.querySelector(
153 '.sui-progress-state-text'
154 );
155 if (progress >= 90) {
156 statusDiv.innerHTML = 'Finalizing...';
157 } else if (cancel) {
158 statusDiv.innerHTML = 'Cancelling...';
159 } else {
160 statusDiv.innerHTML =
161 this.currentStep +
162 '/' +
163 this.totalSteps +
164 ' ' +
165 'images restored';
166 }
167 },
168
169 /**
170 * First step in bulk restore - get the bulk attachment count.
171 */
172 initScan() {
173 const self = this;
174 const _nonce = document.getElementById('_wpnonce');
175
176 const xhr = new XMLHttpRequest();
177 xhr.open('POST', ajaxurl + '?action=get_image_count', true);
178 xhr.setRequestHeader(
179 'Content-type',
180 'application/x-www-form-urlencoded'
181 );
182 xhr.onload = () => {
183 if (200 === xhr.status) {
184 const res = JSON.parse(xhr.response);
185 if ('undefined' !== typeof res.data.items) {
186 self.items = res.data.items;
187 self.totalSteps = res.data.items.length;
188 self.step();
189 }
190 } else {
191 window.console.log(
192 'Request failed. Returned status of ' + xhr.status
193 );
194 }
195 };
196 xhr.send('_ajax_nonce=' + _nonce.value);
197 },
198
199 /**
200 * Execute a scan step recursively
201 */
202 step() {
203 const self = this;
204 const _nonce = document.getElementById('_wpnonce');
205
206 if (0 < this.items.length) {
207 const item = this.items.pop();
208 const xhr = new XMLHttpRequest();
209 xhr.open('POST', ajaxurl + '?action=restore_step', true);
210 xhr.setRequestHeader(
211 'Content-type',
212 'application/x-www-form-urlencoded'
213 );
214 xhr.onload = () => {
215 this.currentStep++;
216
217 if (200 === xhr.status) {
218 const res = JSON.parse(xhr.response);
219 const data = ((res || {}).data || {});
220 if (data.success) {
221 self.success.push(item);
222 } else {
223 self.errors.push({
224 id: item,
225 src: data.src || "Error",
226 thumb: data.thumb,
227 link: data.link,
228 });
229 }
230 }
231
232 self.updateProgressBar();
233 self.step();
234 };
235 xhr.send('item=' + item + '&_ajax_nonce=' + _nonce.value);
236 } else {
237 // Finish.
238 this.settings = {
239 slide: 'finish',
240 success: this.success.length,
241 errors: this.errors,
242 total: this.totalSteps,
243 };
244
245 self.renderTemplate();
246 if (0 < this.errors.length) {
247 this.modal.style.maxWidth = '660px';
248 this.modal.querySelector('.sui-box').style.maxWidth =
249 '660px';
250 }
251 }
252 },
253 };
254
255 /**
256 * Template function (underscores based).
257 *
258 * @type {Function}
259 */
260 WP_Smush.restore.template = _.memoize((id) => {
261 let compiled;
262 const options = {
263 evaluate: /<#([\s\S]+?)#>/g,
264 interpolate: /{{{([\s\S]+?)}}}/g,
265 escape: /{{([^}]+?)}}(?!})/g,
266 variable: 'data',
267 };
268
269 return (data) => {
270 _.templateSettings = options;
271 compiled =
272 compiled || _.template(document.getElementById(id).innerHTML);
273 return compiled(data);
274 };
275 });
276 })();
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Bulk Smush functionality.
6 *
7 * @since 2.9.0 Moved from admin.js
8 */
9
10 import Smush from '../smush/smush';
11 import Fetcher from '../utils/fetcher';
12 import SmushProcess from '../common/progressbar';
13
14 ( function( $ ) {
15 'use strict';
16
17 WP_Smush.bulk = {
18 init() {
19 this.onClickBulkSmushNow();
20 this.onClickIgnoreImage();
21 this.onClickIgnoreAllImages();
22 this.onScanCompleted();
23 },
24 onClickBulkSmushNow() {
25 /**
26 * Handle the Bulk Smush/Bulk re-Smush button click.
27 */
28 $( '.wp-smush-all' ).on( 'click', function( e ) {
29 const bulkSmushButton = $(this);
30 if ( bulkSmushButton.hasClass('wp-smush-scan-and-bulk-smush') ) {
31 return;
32 }
33 e.preventDefault();
34
35 WP_Smush.bulk.ajaxBulkSmushStart( bulkSmushButton );
36 } );
37 },
38 ajaxBulkSmushStart( bulkSmushButton ) {
39 bulkSmushButton = bulkSmushButton || $( '#wp-smush-bulk-content .wp-smush-all' );
40 // Check for IDs, if there is none (unsmushed or lossless), don't call Smush function.
41 /** @param {Array} wp_smushit_data.unsmushed */
42 if (
43 'undefined' === typeof window.wp_smushit_data ||
44 ( 0 === window.wp_smushit_data.unsmushed.length &&
45 0 === window.wp_smushit_data.resmush.length )
46 ) {
47 return false;
48 }
49 // Disable re-Smush and scan button.
50 // TODO: refine what is disabled.
51 $(
52 '.wp-resmush.wp-smush-action, .wp-smush-scan, .wp-smush-all:not(.sui-progress-close), a.wp-smush-lossy-enable, button.wp-smush-resize-enable, button#save-settings-button'
53 ).prop( 'disabled', true );
54
55 if ( bulkSmushButton.hasClass('wp-smush-resume-bulk-smush') && this.bulkSmush ) {
56 this.resumeBulkSmush();
57 return;
58 }
59
60 this.bulkSmush = new Smush( bulkSmushButton, true );
61 SmushProcess.setOnCancelCallback( () => {
62 this.bulkSmush.cancelAjax()
63 }).update( 0, this.bulkSmush.ids.length ).show();
64
65 // Show upsell cdn.
66 this.maybeShowCDNUpsellForPreSiteOnStart();
67
68 // Run bulk Smush.
69 this.bulkSmush.run();
70 },
71 resumeBulkSmush() {
72 SmushProcess.disableExceedLimitMode();
73 SmushProcess.hideBulkSmushDescription();
74 this.bulkSmush.onStart();
75 this.bulkSmush.callAjax();
76 },
77 onClickIgnoreImage() {
78 /**
79 * Ignore file from bulk Smush.
80 *
81 * @since 2.9.0
82 */
83 $( 'body' ).on( 'click', '.smush-ignore-image', function( e ) {
84 e.preventDefault();
85
86 const self = $( this );
87
88 self.prop( 'disabled', true );
89 self.attr( 'data-tooltip' );
90 self.removeClass( 'sui-tooltip' );
91 $.post( ajaxurl, {
92 action: 'ignore_bulk_image',
93 id: self.attr( 'data-id' ),
94 _ajax_nonce: wp_smush_msgs.nonce,
95 } ).done( ( response ) => {
96 if ( self.is( 'a' ) && response.success && 'undefined' !== typeof response.data.html ) {
97 if( self.closest('.smush-status-links') ) {
98 self.closest('.smush-status-links').parent().html( response.data.html );
99 } else if (e.target.closest( '.smush-bulk-error-row' ) ){
100 self.addClass('disabled');
101 e.target.closest( '.smush-bulk-error-row' ).style.opacity = 0.5;
102 }
103 }
104 } );
105 } );
106 },
107 onClickIgnoreAllImages() {
108 /**
109 * Ignore file from bulk Smush.
110 *
111 * @since 3.12.0
112 */
113 const ignoreAll = document.querySelector('.wp_smush_ignore_all_failed_items');
114 if ( ignoreAll ) {
115 ignoreAll.onclick = (e) => {
116 e.preventDefault();
117 e.target.setAttribute('disabled','');
118 e.target.style.cursor = 'progress';
119 const type = e.target.dataset.type || null;
120 e.target.classList.remove('sui-tooltip');
121 Fetcher.smush.ignoreAll(type).then((res) => {
122 if ( res.success ) {
123 window.location.reload();
124 } else {
125 e.target.style.cursor = 'pointer';
126 e.target.removeAttribute('disabled');
127 WP_Smush.helpers.showNotice( res );
128 }
129 });
130 }
131 }
132 },
133 onScanCompleted() {
134 document.addEventListener('ajaxBulkSmushOnScanCompleted', (e) => {
135 this.ajaxBulkSmushStart();
136 });
137 },
138 maybeShowCDNUpsellForPreSiteOnStart: () => {
139 // Show upsell cdn.
140 const upsell_cdn = document.querySelector('.wp-smush-upsell-cdn');
141 if ( upsell_cdn ) {
142 upsell_cdn.classList.remove('sui-hidden');
143 }
144 }
145
146 };
147
148 WP_Smush.bulk.init();
149 } )( jQuery );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Directory Smush module JavaScript code.
6 *
7 * @since 2.8.1 Separated from admin.js into dedicated file.
8 */
9
10 import { createTree } from 'jquery.fancytree';
11 import Scanner from '../smush/directory-scanner';
12
13 ( function( $ ) {
14 'use strict';
15
16 WP_Smush.directory = {
17 selected: [],
18 tree: [],
19 wp_smush_msgs: [],
20 triggered: false,
21
22 init() {
23 const self = this,
24 progressDialog = $( '#wp-smush-progress-dialog' );
25
26 let totalSteps = 0,
27 currentScanStep = 0;
28
29 // Make sure directory smush vars are set.
30 if ( typeof window.wp_smushit_data.dir_smush !== 'undefined' ) {
31 totalSteps = window.wp_smushit_data.dir_smush.totalSteps;
32 currentScanStep =
33 window.wp_smushit_data.dir_smush.currentScanStep;
34 }
35
36 // Init image scanner.
37 this.scanner = new Scanner( totalSteps, currentScanStep );
38
39 /**
40 * Smush translation strings.
41 *
42 * @param {Array} wp_smush_msgs
43 */
44 this.wp_smush_msgs = window.wp_smush_msgs || {};
45
46 /**
47 * Open the "Select Smush directory" modal.
48 */
49 $( 'button.wp-smush-browse, a#smush-directory-open-modal' ).on(
50 'click',
51 function( e ) {
52 e.preventDefault();
53
54 if ( $( e.currentTarget ).hasClass( 'wp-smush-browse' ) ) {
55 // Hide all the notices.
56 $( 'div.wp-smush-scan-result div.wp-smush-notice' ).hide();
57
58 // Remove notice.
59 $( 'div.wp-smush-info' ).remove();
60 }
61
62 window.SUI.openModal(
63 'wp-smush-list-dialog',
64 e.currentTarget,
65 $(
66 '#wp-smush-list-dialog .sui-box-header [data-modal-close]'
67 )[0],
68 true
69 );
70 //Display File tree for Directory Smush
71 self.initFileTree();
72 }
73 );
74
75 /**
76 * Smush images: Smush in Choose Directory modal clicked
77 */
78 $( '#wp-smush-select-dir' ).on( 'click', function( e ) {
79 e.preventDefault();
80
81 $( 'div.wp-smush-list-dialog div.sui-box-body' ).css( {
82 opacity: '0.8',
83 } );
84 $( 'div.wp-smush-list-dialog div.sui-box-body a' ).off(
85 'click'
86 );
87
88 const button = $( this );
89
90 // Display the spinner.
91 button.addClass('sui-button-onload');
92
93 const selectedFolders = self.tree.getSelectedNodes();
94
95 const paths = [];
96 selectedFolders.forEach( function( folder ) {
97 paths.push( folder.key );
98 } );
99
100 // Send a ajax request to get a list of all the image files
101 const param = {
102 action: 'image_list',
103 smush_path: paths,
104 image_list_nonce: $(
105 'input[name="image_list_nonce"]'
106 ).val(),
107 };
108
109 $.post( ajaxurl, param, function( response ) {
110 if ( response.success ) {
111 // Close the modal.
112 window.SUI.closeModal();
113
114 self.scanner = new Scanner( response.data, 0 );
115 self.showProgressDialog( response.data );
116 self.scanner.scan();
117 } else {
118 // Remove the spinner.
119 button.removeClass('sui-button-onload');
120
121 window.SUI.openNotice(
122 'wp-smush-ajax-notice',
123 response.data.message,
124 { type: 'warning' }
125 );
126 }
127 } );
128 } );
129
130 /**
131 * Cancel scan.
132 */
133 progressDialog.on(
134 'click',
135 '#cancel-directory-smush, #dialog-close-div, .wp-smush-cancel-dir',
136 function( e ) {
137 e.preventDefault();
138 // Display the spinner
139 $( '.wp-smush-cancel-dir' ).addClass( 'sui-button-onload' );
140 self.scanner
141 .cancel()
142 .done(
143 () =>
144 ( window.location.href =
145 self.wp_smush_msgs.directory_url )
146 );
147 }
148 );
149
150 /**
151 * Continue scan.
152 */
153 progressDialog.on(
154 'click',
155 '.sui-icon-play, .wp-smush-resume-scan',
156 function( e ) {
157 e.preventDefault();
158 self.scanner.resume();
159 }
160 );
161
162 /**
163 * Check to see if we should open the directory module.
164 * Used to redirect from dashboard page.
165 *
166 * @since 3.8.6
167 */
168 const queryString = window.location.search;
169 const urlParams = new URLSearchParams( queryString );
170 if ( urlParams.has( 'start' ) && ! this.triggered ) {
171 this.triggered = true;
172 $( 'button.wp-smush-browse' ).trigger( 'click' );
173 }
174 },
175
176 /**
177 * Init fileTree.
178 */
179 initFileTree() {
180 const self = this,
181 smushButton = $( 'button#wp-smush-select-dir' ),
182 ajaxSettings = {
183 type: 'GET',
184 url: ajaxurl,
185 data: {
186 action: 'smush_get_directory_list',
187 list_nonce: $( 'input[name="list_nonce"]' ).val(),
188 },
189 cache: false,
190 };
191
192 // Object already defined.
193 if ( Object.entries( self.tree ).length > 0 ) {
194 return;
195 }
196
197 self.tree = createTree( '.wp-smush-list-dialog .content', {
198 autoCollapse: true, // Automatically collapse all siblings, when a node is expanded
199 clickFolderMode: 3, // 1:activate, 2:expand, 3:activate and expand, 4:activate (dblclick expands)
200 checkbox: true, // Show checkboxes
201 debugLevel: 0, // 0:quiet, 1:errors, 2:warnings, 3:infos, 4:debug
202 selectMode: 3, // 1:single, 2:multi, 3:multi-hier
203 tabindex: '0', // Whole tree behaves as one single control
204 keyboard: true, // Support keyboard navigation
205 quicksearch: true, // Navigate to next node by typing the first letters
206 source: ajaxSettings,
207 lazyLoad: ( event, data ) => {
208 data.result = new Promise( function( resolve, reject ) {
209 ajaxSettings.data.dir = data.node.key;
210 $.ajax( ajaxSettings )
211 .done( ( response ) => resolve( response ) )
212 .fail( reject );
213 } );
214 },
215 loadChildren: ( event, data ) =>
216 data.node.fixSelection3AfterClick(), // Apply parent's state to new child nodes:
217 select: () =>
218 smushButton.prop(
219 'disabled',
220 ! +self.tree.getSelectedNodes().length
221 ),
222 init: () => smushButton.prop( 'disabled', true ),
223 } );
224 },
225
226 /**
227 * Show progress dialog.
228 *
229 * @param {number} items Number of items in the scan.
230 */
231 showProgressDialog( items ) {
232 // Update items status and show the progress dialog..
233 $( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
234 '0/' + items + ' ' + self.wp_smush_msgs.progress_smushed
235 );
236
237 window.SUI.openModal(
238 'wp-smush-progress-dialog',
239 'dialog-close-div',
240 undefined,
241 false
242 );
243 },
244
245 /**
246 * Update progress bar during directory smush.
247 *
248 * @param {number} progress Current progress in percent.
249 * @param {boolean} cancel Cancel status.
250 */
251 updateProgressBar( progress, cancel = false ) {
252 if ( progress > 100 ) {
253 progress = 100;
254 }
255
256 // Update progress bar
257 $( '.sui-progress-block .sui-progress-text span' ).text(
258 progress + '%'
259 );
260 $( '.sui-progress-block .sui-progress-bar span' ).width(
261 progress + '%'
262 );
263
264 if ( progress >= 90 ) {
265 $( '.sui-progress-state .sui-progress-state-text' ).text(
266 'Finalizing...'
267 );
268 }
269
270 if ( cancel ) {
271 $( '.sui-progress-state .sui-progress-state-text' ).text(
272 'Cancelling...'
273 );
274 }
275 },
276 };
277
278 WP_Smush.directory.init();
279 }( jQuery ) );
1 /* global WP_Smush */
2 /* global ajaxurl */
3 /* global wp_smush_msgs */
4
5 /**
6 * Helpers functions.
7 *
8 * @since 2.9.0 Moved from admin.js
9 */
10 ( function() {
11 'use strict';
12
13 WP_Smush.helpers = {
14 init: () => {},
15 cacheUpsellErrorCodes: [],
16
17 /**
18 * Convert bytes to human-readable form.
19 *
20 * @param {number} a Bytes
21 * @param {number} b Number of digits
22 * @return {*} Formatted Bytes
23 */
24 formatBytes: ( a, b ) => {
25 const thresh = 1024,
26 units = [ 'KB', 'MB', 'GB', 'TB', 'PB' ];
27
28 if ( Math.abs( a ) < thresh ) {
29 return a + ' B';
30 }
31
32 let u = -1;
33
34 do {
35 a /= thresh;
36 ++u;
37 } while ( Math.abs( a ) >= thresh && u < units.length - 1 );
38
39 return a.toFixed( b ) + ' ' + units[ u ];
40 },
41
42 /**
43 * Get size from a string.
44 *
45 * @param {string} formattedSize Formatter string
46 * @return {*} Formatted Bytes
47 */
48 getSizeFromString: ( formattedSize ) => {
49 return formattedSize.replace( /[a-zA-Z]/g, '' ).trim();
50 },
51
52 /**
53 * Get type from formatted string.
54 *
55 * @param {string} formattedSize Formatted string
56 * @return {*} Formatted Bytes
57 */
58 getFormatFromString: ( formattedSize ) => {
59 return formattedSize.replace( /[0-9.]/g, '' ).trim();
60 },
61
62 /**
63 * Stackoverflow: http://stackoverflow.com/questions/1726630/formatting-a-number-with-exactly-two-decimals-in-javascript
64 *
65 * @param {number} num
66 * @param {number} decimals
67 * @return {number} Number
68 */
69 precise_round: ( num, decimals ) => {
70 const sign = num >= 0 ? 1 : -1;
71 // Keep the percentage below 100.
72 num = num > 100 ? 100 : num;
73 return (
74 Math.round( num * Math.pow( 10, decimals ) + sign * 0.001 ) /
75 Math.pow( 10, decimals )
76 );
77 },
78
79 /**
80 * Displays a floating error message using the #wp-smush-ajax-notice container.
81 *
82 * @since 3.8.0
83 *
84 * @param {string} message
85 */
86 showErrorNotice: ( message ) => {
87 if ( 'undefined' === typeof message ) {
88 return;
89 }
90
91 const noticeMessage = `<p>${ message }</p>`,
92 noticeOptions = {
93 type: 'error',
94 icon: 'info',
95 };
96
97 SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
98
99 const loadingButton = document.querySelector( '.sui-button-onload' );
100 if ( loadingButton ) {
101 loadingButton.classList.remove( 'sui-button-onload' );
102 }
103 },
104
105 /**
106 * Reset settings.
107 *
108 * @since 3.2.0
109 */
110 resetSettings: () => {
111 const _nonce = document.getElementById( 'wp_smush_reset' );
112 const xhr = new XMLHttpRequest();
113 xhr.open( 'POST', ajaxurl + '?action=reset_settings', true );
114 xhr.setRequestHeader(
115 'Content-type',
116 'application/x-www-form-urlencoded'
117 );
118 xhr.onload = () => {
119 if ( 200 === xhr.status ) {
120 const res = JSON.parse( xhr.response );
121 if ( 'undefined' !== typeof res.success && res.success ) {
122 window.location.href = wp_smush_msgs.smush_url;
123 }
124 } else {
125 window.console.log(
126 'Request failed. Returned status of ' + xhr.status
127 );
128 }
129 };
130 xhr.send( '_ajax_nonce=' + _nonce.value );
131 },
132
133 /**
134 * Prepare error row. Will only allow to hide errors for WP media attachments (not nextgen).
135 *
136 * @since 1.9.0
137 * @since 3.12.0 Moved from Smush.
138 *
139 * @param {string} errorMsg Error message.
140 * @param {string} fileName File name.
141 * @param {string} thumbnail Thumbnail for image (if available).
142 * @param {number} id Image ID.
143 * @param {string} type Smush type: media or netxgen.
144 * @param {string} errorCode Error code.
145 *
146 * @return {string} Row with error.
147 */
148 prepareBulkSmushErrorRow: (errorMsg, fileName, thumbnail, id, type, errorCode) => {
149 const thumbDiv =
150 thumbnail && 'undefined' !== typeof thumbnail ?
151 `<img class="attachment-thumbnail" src="${thumbnail}" />` :
152 '<i class="sui-icon-photo-picture" aria-hidden="true"></i>';
153 const editLink = window.wp_smush_msgs.edit_link.replace('{{id}}', id);
154 fileName =
155 'undefined' === fileName || 'undefined' === typeof fileName ?
156 'undefined' :
157 fileName;
158
159 let tableDiv =
160 `<div class="smush-bulk-error-row" data-error-code="${errorCode}">
161 <div class="smush-bulk-image-data">
162 <div class="smush-bulk-image-title">
163 ${ thumbDiv }
164 <span class="smush-image-name">
165 <a href="${editLink}">${fileName}</a>
166 </span>
167 </div>
168 <div class="smush-image-error">
169 ${errorMsg}
170 </div>
171 </div>`;
172
173 if ('media' === type) {
174 tableDiv +=
175 `<div class="smush-bulk-image-actions">
176 <a href="javascript:void(0)" class="sui-tooltip sui-tooltip-constrained sui-tooltip-left smush-ignore-image" data-tooltip="${window.wp_smush_msgs.error_ignore}" data-id="${id}">
177 ${window.wp_smush_msgs.btn_ignore}
178 </a>
179 <a class="smush-link-detail" href="${editLink}">
180 ${window.wp_smush_msgs.view_detail}
181 </a>
182 </div>`;
183 }
184
185 tableDiv += '</div>';
186
187 tableDiv += WP_Smush.helpers.upsellWithError( errorCode );
188
189 return tableDiv;
190 },
191 cacheUpsellErrorCode( errorCode ) {
192 this.cacheUpsellErrorCodes.push( errorCode );
193 },
194 /**
195 * Get upsell base on error code.
196 * @param {string} errorCode Error code.
197 * @returns {string}
198 */
199 upsellWithError(errorCode) {
200 if (
201 !errorCode
202 || !window.wp_smush_msgs['error_' + errorCode]
203 || this.isUpsellRendered( errorCode )
204 ) {
205 return '';
206 }
207 this.cacheRenderedUpsell( errorCode );
208
209 return '<div class="smush-bulk-error-row smush-error-upsell">' +
210 '<div class="smush-bulk-image-title">' +
211 '<span class="smush-image-error">' +
212 window.wp_smush_msgs['error_' + errorCode] +
213 '</span>' +
214 '</div></div>';
215 },
216 // Do not use arrow function to use `this`.
217 isUpsellRendered( errorCode ) {
218 return this.cacheUpsellErrorCodes.includes( errorCode );
219 },
220 // Do not use arrow function to use `this`.
221 cacheRenderedUpsell( errorCode ) {
222 this.cacheUpsellErrorCodes.push( errorCode );
223 },
224 /**
225 * Get error message from Ajax response or Error.
226 * @param {Object} resp
227 */
228 getErrorMessage: ( resp ) => {
229 return resp.message || resp.data && resp.data.message ||
230 resp.responseJSON && resp.responseJSON.data && resp.responseJSON.data.message ||
231 window.wp_smush_msgs.generic_ajax_error ||
232 resp.status && 'Request failed. Returned status of ' + resp.status
233 },
234
235 /**
236 * Displays a floating message from response,
237 * using the #wp-smush-ajax-notice container.
238 *
239 * @param {Object|string} notice
240 * @param {Object} noticeOptions
241 */
242 showNotice: function( notice, noticeOptions ) {
243 let message;
244 if ( 'object' === typeof notice ) {
245 message = this.getErrorMessage( notice );
246 } else {
247 message = notice;
248 }
249
250 if ( ! message ) {
251 return;
252 }
253
254 noticeOptions = noticeOptions || {};
255 noticeOptions = Object.assign({
256 showdismiss: false,
257 autoclose: true,
258 },noticeOptions);
259 noticeOptions = {
260 type: noticeOptions.type || 'error',
261 icon: noticeOptions.icon || ( 'success' === noticeOptions.type ? 'check-tick' : 'info' ),
262 dismiss: {
263 show: noticeOptions.showdismiss,
264 label: window.wp_smush_msgs.noticeDismiss,
265 tooltip: window.wp_smush_msgs.noticeDismissTooltip,
266 },
267 autoclose: {
268 show: noticeOptions.autoclose
269 }
270 };
271
272 const noticeMessage = `<p>${ message }</p>`;
273
274 SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
275 return Promise.resolve( '#wp-smush-ajax-notice' );
276 },
277 closeNotice() {
278 window.SUI.closeNotice( 'wp-smush-ajax-notice' );
279 },
280 renderActivationCDNNotice: function( noticeMessage ) {
281 const animatedNotice = document.getElementById('wp-smush-animated-upsell-notice');
282 if ( animatedNotice ) {
283 return;
284 }
285 const upsellHtml = `<div class="sui-notice sui-notice-info sui-margin-top" id="wp-smush-animated-upsell-notice">
286 <div class="sui-notice-content">
287 <div class="sui-notice-message">
288 <i class="sui-notice-icon sui-icon-info" aria-hidden="true"></i>
289 <p>${noticeMessage}</p>
290 </div>
291 </div>
292 </div>`;
293 document.querySelector( '#smush-box-bulk .wp-smush-bulk-wrapper' ).outerHTML += upsellHtml;
294 }
295 };
296
297 WP_Smush.helpers.init();
298 }() );
1 /* global WP_Smush */
2
3 /**
4 * Scan Media Library.
5 *
6 */
7 import MediaLibraryScanner from '../common/media-library-scanner';
8
9 ( function() {
10 'use strict';
11 if ( ! window.wp_smush_msgs ) {
12 return;
13 }
14 const $ = document.querySelector.bind( document );
15 const existScanProgressBar = $( '.wp-smush-scan-progress-bar-wrapper' );
16 if ( ! existScanProgressBar ) {
17 return;
18 }
19
20 const recheckImagesBtn = $( '.wp-smush-scan' );
21 if ( recheckImagesBtn ) {
22 return;
23 }
24 //Check scan is running.
25 const is_scan_running = window.wp_smushit_data.media_library_scan?.in_processing;
26 if ( ! is_scan_running ) {
27 return;
28 }
29
30 const { __ } = wp.i18n;
31
32 class mediaLibraryScannerOnDashboard extends MediaLibraryScanner {
33 constructor() {
34 super();
35 this.bulkSmushLink = $( '.wp-smush-bulk-smush-link' );
36 }
37 onShowProgressBar() {
38 this.disableBulkSmushLink();
39 }
40
41 onCloseProgressBar() {
42 this.revertBulkSmushLink();
43 }
44
45 disableBulkSmushLink() {
46 if ( ! this.bulkSmushLink ) {
47 return;
48 }
49 this.bulkSmushLink.setAttribute( 'disabled', true );
50 this.setInnerText( this.bulkSmushLink, __( 'Waiting for Re-check to finish', 'wp-smushit' ) );
51 }
52
53 revertBulkSmushLink() {
54 if ( ! this.bulkSmushLink ) {
55 return;
56 }
57 this.bulkSmushLink.removeAttribute( 'disabled' );
58 this.revertInnerText( this.bulkSmushLink );
59 }
60 }
61
62 ( new mediaLibraryScannerOnDashboard() ).showProgressBar().autoSyncStatus();
63 }() );
1 import Smush from '../smush/smush';
2 import SmushProcess from '../common/progressbar';
3
4 (function($) {
5 $(function() {
6 /** Handle NextGen Gallery smush button click **/
7 $('body').on('click', '.wp-smush-nextgen-send', function (e) {
8 // prevent the default action
9 e.preventDefault();
10 new Smush($(this), false, 'nextgen');
11 });
12
13 /** Handle NextGen Gallery Bulk smush button click **/
14 $('body').on('click', '.wp-smush-nextgen-bulk', function (e) {
15 // prevent the default action
16 e.preventDefault();
17
18 // Remove existing Re-Smush notices.
19 // TODO: REMOVE re-smush-notice since no longer used.
20 $('.wp-smush-resmush-notice').remove();
21
22 //Check for ids, if there is none (Unsmushed or lossless), don't call smush function
23 if (
24 'undefined' === typeof wp_smushit_data ||
25 (wp_smushit_data.unsmushed.length === 0 &&
26 wp_smushit_data.resmush.length === 0)
27 ) {
28 return false;
29 }
30
31 const bulkSmush = new Smush( $(this), true, 'nextgen' );
32 SmushProcess.setOnCancelCallback( () => {
33 bulkSmush.cancelAjax();
34 }).update( 0, bulkSmush.ids.length ).show();
35
36 jQuery('.wp-smush-all, .wp-smush-scan').prop('disabled', true);
37 $('.wp-smush-notice.wp-smush-remaining').hide();
38
39 // Run bulk Smush.
40 bulkSmush.run();
41 })
42 .on('click', '.wp-smush-trigger-nextgen-bulk', function(e){
43 e.preventDefault();
44 const bulkSmushButton = $('.wp-smush-nextgen-bulk');
45 if ( bulkSmushButton.length ) {
46 bulkSmushButton.trigger('click');
47 SUI.closeNotice( 'wp-smush-ajax-notice' );
48 }
49 });
50
51 });
52 }(window.jQuery));
...\ No newline at end of file ...\ No newline at end of file
1 /* global ajaxurl */
2 /* global wp_smush_msgs */
3
4 ( function( $ ) {
5 'use strict';
6
7 const s3alert = $( '#wp-smush-s3support-alert' );
8
9 /**
10 * S3 support alert.
11 *
12 * @since 3.6.2 Moved from class-s3.php
13 */
14 if ( s3alert.length ) {
15 const noticeOptions = {
16 type: 'warning',
17 icon: 'info',
18 dismiss: {
19 show: true,
20 label: wp_smush_msgs.noticeDismiss,
21 tooltip: wp_smush_msgs.noticeDismissTooltip,
22 },
23 };
24
25 window.SUI.openNotice(
26 'wp-smush-s3support-alert',
27 s3alert.data( 'message' ),
28 noticeOptions
29 );
30 }
31
32 // Dismiss S3 support alert.
33 s3alert.on( 'click', 'button', () => {
34 $.post( ajaxurl,
35 {
36 action: 'dismiss_s3support_alert',
37 _ajax_nonce: window.wp_smush_msgs.nonce,
38 }
39 );
40 } );
41
42 // Remove API message.
43 $( '#wp-smush-api-message button.sui-button-icon' ).on( 'click', function( e ) {
44 e.preventDefault();
45 const notice = $( '#wp-smush-api-message' );
46 notice.slideUp( 'slow', function() {
47 notice.remove();
48 } );
49 $.post( ajaxurl,
50 {
51 action: 'hide_api_message',
52 _ajax_nonce: window.wp_smush_msgs.nonce,
53 }
54 );
55 } );
56
57 // Hide the notice after a CTA button was clicked
58 function removeNotice( e ) {
59 const $notice = $( e.currentTarget ).closest( '.smush-notice' );
60 $notice.fadeTo( 100, 0, () =>
61 $notice.slideUp( 100, () => $notice.remove() )
62 );
63 }
64
65 // Only used for the Dashboard notification for now.
66 $( '.smush-notice .smush-notice-act' ).on( 'click', ( e ) => {
67 removeNotice( e );
68 } );
69
70 // Dismiss the update notice.
71 $( '.wp-smush-update-info' ).on( 'click', '.notice-dismiss', ( e ) => {
72 e.preventDefault();
73 removeNotice( e );
74 $.post( ajaxurl,
75 {
76 action: 'dismiss_update_info',
77 _ajax_nonce: window.wp_smush_msgs.nonce,
78 }
79 );
80 } );
81 }( jQuery ) );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Modals JavaScript code.
6 */
7 ( function() {
8 'use strict';
9
10 /**
11 * Onboarding modal.
12 *
13 * @since 3.1
14 */
15 WP_Smush.onboarding = {
16 membership: 'free', // Assume free by default.
17 onboardingModal: document.getElementById( 'smush-onboarding-dialog' ),
18 first_slide: 'usage',
19 settings: {
20 first: true,
21 last: false,
22 slide: 'usage',
23 value: false,
24 },
25 selection: {
26 usage: false,
27 auto: true,
28 lossy: true,
29 strip_exif: true,
30 original: false,
31 lazy_load: true,
32 },
33 contentContainer: document.getElementById( 'smush-onboarding-content' ),
34 onboardingSlides: [
35 'usage',
36 'auto',
37 'lossy',
38 'strip_exif',
39 'original',
40 'lazy_load',
41 ],
42 touchX: null,
43 touchY: null,
44 recheckImagesLink: '',
45
46 /**
47 * Init module.
48 */
49 init() {
50 if ( ! this.onboardingModal ) {
51 return;
52 }
53
54 const dialog = document.getElementById( 'smush-onboarding' );
55
56 this.membership = dialog.dataset.type;
57 this.recheckImagesLink = dialog.dataset.ctaUrl;
58
59 if ( 'pro' !== this.membership ) {
60 this.onboardingSlides = [
61 'usage',
62 'auto',
63 'lossy',
64 'strip_exif',
65 'lazy_load',
66 ];
67 }
68
69 if ( 'false' === dialog.dataset.tracking ) {
70 this.onboardingSlides.pop();
71 }
72
73 this.renderTemplate();
74
75 // Skip setup.
76 const skipButton = this.onboardingModal.querySelector(
77 '.smush-onboarding-skip-link'
78 );
79 if ( skipButton ) {
80 skipButton.addEventListener( 'click', this.skipSetup.bind( this ) );
81 }
82
83 // Show the modal.
84 window.SUI.openModal(
85 'smush-onboarding-dialog',
86 'wpcontent',
87 undefined,
88 false
89 );
90 },
91
92 /**
93 * Get swipe coordinates.
94 *
95 * @param {Object} e
96 */
97 handleTouchStart( e ) {
98 const firstTouch = e.touches[ 0 ];
99 this.touchX = firstTouch.clientX;
100 this.touchY = firstTouch.clientY;
101 },
102
103 /**
104 * Process swipe left/right.
105 *
106 * @param {Object} e
107 */
108 handleTouchMove( e ) {
109 if ( ! this.touchX || ! this.touchY ) {
110 return;
111 }
112
113 const xUp = e.touches[ 0 ].clientX,
114 yUp = e.touches[ 0 ].clientY,
115 xDiff = this.touchX - xUp,
116 yDiff = this.touchY - yUp;
117
118 if ( Math.abs( xDiff ) > Math.abs( yDiff ) ) {
119 if ( xDiff > 0 ) {
120 if ( false === WP_Smush.onboarding.settings.last ) {
121 WP_Smush.onboarding.next( null, 'next' );
122 }
123 } else if ( false === WP_Smush.onboarding.settings.first ) {
124 WP_Smush.onboarding.next( null, 'prev' );
125 }
126 }
127
128 this.touchX = null;
129 this.touchY = null;
130 },
131
132 /**
133 * Update the template, register new listeners.
134 *
135 * @param {string} directionClass Accepts: fadeInRight, fadeInLeft, none.
136 */
137 renderTemplate( directionClass = 'none' ) {
138 // Grab the selected value.
139 const input = this.onboardingModal.querySelector(
140 'input[type="checkbox"]'
141 );
142 if ( input ) {
143 this.selection[ input.id ] = input.checked;
144 }
145
146 const template = WP_Smush.onboarding.template( 'smush-onboarding' );
147 const content = template( this.settings );
148
149 if ( content ) {
150 this.contentContainer.innerHTML = content;
151
152 if ( 'none' === directionClass ) {
153 this.contentContainer.classList.add( 'loaded' );
154 } else {
155 this.contentContainer.classList.remove( 'loaded' );
156 this.contentContainer.classList.add( directionClass );
157 setTimeout( () => {
158 this.contentContainer.classList.add( 'loaded' );
159 this.contentContainer.classList.remove(
160 directionClass
161 );
162 }, 600 );
163 }
164 }
165
166 this.onboardingModal.addEventListener(
167 'touchstart',
168 this.handleTouchStart,
169 false
170 );
171 this.onboardingModal.addEventListener(
172 'touchmove',
173 this.handleTouchMove,
174 false
175 );
176
177 this.bindSubmit();
178 },
179
180 /**
181 * Catch "Finish setup wizard" button click.
182 */
183 bindSubmit() {
184 const submitButton = this.onboardingModal.querySelector(
185 'button[type="submit"]'
186 );
187 const self = this;
188
189 if ( submitButton ) {
190 submitButton.addEventListener( 'click', function( e ) {
191 e.preventDefault();
192
193 // Because we are not rendering the template, we need to update the last element value.
194 const input = self.onboardingModal.querySelector(
195 'input[type="checkbox"]'
196 );
197 if ( input ) {
198 self.selection[ input.id ] = input.checked;
199 }
200
201 const _nonce = document.getElementById(
202 'smush_quick_setup_nonce'
203 );
204
205 const xhr = new XMLHttpRequest();
206 xhr.open( 'POST', ajaxurl + '?action=smush_setup', true );
207 xhr.setRequestHeader(
208 'Content-type',
209 'application/x-www-form-urlencoded'
210 );
211 xhr.onload = () => {
212 if ( 200 === xhr.status ) {
213 self.onFinishingSetup();
214 } else {
215 window.console.log(
216 'Request failed. Returned status of ' +
217 xhr.status
218 );
219 }
220 };
221 xhr.send(
222 'smush_settings=' +
223 JSON.stringify( self.selection ) +
224 '&_ajax_nonce=' +
225 _nonce.value
226 );
227 } );
228 }
229 },
230
231 onFinishingSetup() {
232 this.onFinish();
233 this.startRecheckImages();
234 },
235
236 onFinish() {
237 window.SUI.closeModal();
238 },
239
240 startRecheckImages() {
241 if ( ! this.recheckImagesLink ) {
242 return;
243 }
244 window.location.href = this.recheckImagesLink;
245 },
246
247 /**
248 * Handle navigation.
249 *
250 * @param {Object} e
251 * @param {null|string} whereTo
252 */
253 next( e, whereTo = null ) {
254 const index = this.onboardingSlides.indexOf( this.settings.slide );
255 let newIndex = 0;
256
257 if ( ! whereTo ) {
258 newIndex =
259 null !== e && e.classList.contains( 'next' )
260 ? index + 1
261 : index - 1;
262 } else {
263 newIndex = 'next' === whereTo ? index + 1 : index - 1;
264 }
265
266 const directionClass =
267 null !== e && e.classList.contains( 'next' )
268 ? 'fadeInRight'
269 : 'fadeInLeft';
270
271 this.settings = {
272 first: 0 === newIndex,
273 last: newIndex + 1 === this.onboardingSlides.length, // length !== index
274 slide: this.onboardingSlides[ newIndex ],
275 value: this.selection[ this.onboardingSlides[ newIndex ] ],
276 };
277
278 this.renderTemplate( directionClass );
279 },
280
281 /**
282 * Handle circle navigation.
283 *
284 * @param {string} target
285 */
286 goTo( target ) {
287 const newIndex = this.onboardingSlides.indexOf( target );
288
289 this.settings = {
290 first: 0 === newIndex,
291 last: newIndex + 1 === this.onboardingSlides.length, // length !== index
292 slide: target,
293 value: this.selection[ target ],
294 };
295
296 this.renderTemplate();
297 },
298
299 /**
300 * Skip onboarding experience.
301 */
302 skipSetup() {
303 const _nonce = document.getElementById( 'smush_quick_setup_nonce' );
304
305 const xhr = new XMLHttpRequest();
306 xhr.open(
307 'POST',
308 ajaxurl + '?action=skip_smush_setup&_ajax_nonce=' + _nonce.value
309 );
310 xhr.onload = () => {
311 if ( 200 === xhr.status ) {
312 this.onSkipSetup();
313 } else {
314 window.console.log(
315 'Request failed. Returned status of ' + xhr.status
316 );
317 }
318 };
319 xhr.send();
320 },
321
322 onSkipSetup() {
323 this.onFinish();
324 },
325
326 /**
327 * Hide new features modal.
328 *
329 * @param {string} redirectUrl Redirect url after dismissing the new feature modal.
330 * @since 3.7.0
331 * @since 3.12.2 Add a new parameter redirectUrl
332 */
333 hideUpgradeModal: ( redirectUrl ) => {
334 window.SUI.closeModal( 'smush-updated-dialog' );
335 const xhr = new XMLHttpRequest();
336 xhr.open( 'POST', ajaxurl + '?action=hide_new_features&_ajax_nonce=' + window.wp_smush_msgs.nonce );
337 xhr.onload = () => {
338 if ( 200 === xhr.status ) {
339 if ( redirectUrl ) {
340 window.location.href = redirectUrl;
341 }
342 } else {
343 window.console.log(
344 'Request failed. Returned status of ' + xhr.status
345 );
346 }
347 };
348 xhr.send();
349 },
350 };
351
352 /**
353 * Template function (underscores based).
354 *
355 * @type {Function}
356 */
357 WP_Smush.onboarding.template = _.memoize( ( id ) => {
358 let compiled;
359 const options = {
360 evaluate: /<#([\s\S]+?)#>/g,
361 interpolate: /{{{([\s\S]+?)}}}/g,
362 escape: /{{([^}]+?)}}(?!})/g,
363 variable: 'data',
364 };
365
366 return ( data ) => {
367 _.templateSettings = options;
368 compiled =
369 compiled ||
370 _.template( document.getElementById( id ).innerHTML );
371 data.first_slide = WP_Smush.onboarding.first_slide;
372 return compiled( data );
373 };
374 } );
375
376 window.addEventListener( 'load', () => WP_Smush.onboarding.init() );
377 }() );
1 /**
2 * Shared UI JS libraries. Use only what we need to keep the vendor file size smaller.
3 *
4 * @package
5 */
6 require( '@wpmudev/shared-ui/dist/js/_src/code-snippet' );
7 require( '@wpmudev/shared-ui/dist/js/_src/modal-dialog' );
8 require( '@wpmudev/shared-ui/dist/js/_src/notifications' );
9 require( '@wpmudev/shared-ui/dist/js/_src/select2.full' );
10 require( '@wpmudev/shared-ui/dist/js/_src/select2' );
11 require( '@wpmudev/shared-ui/dist/js/_src/tabs' );
12 require( '@wpmudev/shared-ui/dist/js/_src/upload' ); // Used on lazy load page (since 3.2.2).
13 require( '@wpmudev/shared-ui/dist/js/_src/reviews' );
1 /**
2 * BLOCK: extend image block
3 */
4 const { createHigherOrderComponent } = wp.compose,
5 { Fragment } = wp.element,
6 { InspectorControls } = wp.blockEditor,
7 { PanelBody } = wp.components;
8
9 /**
10 * Transform bytes to human readable format.
11 *
12 * @param {number} bytes
13 * @return {string} Readable size string.
14 */
15 function humanFileSize( bytes ) {
16 const thresh = 1024,
17 units = [ 'kB', 'MB', 'GB', 'TB' ];
18
19 if ( Math.abs( bytes ) < thresh ) {
20 return bytes + ' B';
21 }
22
23 let u = -1;
24 do {
25 bytes /= thresh;
26 ++u;
27 } while ( Math.abs( bytes ) >= thresh && u < units.length - 1 );
28
29 return bytes.toFixed( 1 ) + ' ' + units[ u ];
30 }
31
32 /**
33 * Generate Smush stats table.
34 *
35 * @param {number} id
36 * @param {Object} stats
37 * @return {*} Smush stats.
38 */
39 export function smushStats( id, stats ) {
40 if ( 'undefined' === typeof stats ) {
41 return window.smush_vars.strings.gb.select_image;
42 } else if ( 'string' === typeof stats ) {
43 return stats;
44 }
45
46 return (
47 <div
48 id="smush-stats"
49 className="sui-smush-media smush-stats-wrapper hidden"
50 style={ { display: 'block' } }
51 >
52 <table className="wp-smush-stats-holder">
53 <thead>
54 <tr>
55 <th className="smush-stats-header">
56 { window.smush_vars.strings.gb.size }
57 </th>
58 <th className="smush-stats-header">
59 { window.smush_vars.strings.gb.savings }
60 </th>
61 </tr>
62 </thead>
63 <tbody>
64 { Object.keys( stats.sizes )
65 .filter( ( item ) => 0 < stats.sizes[ item ].percent )
66 .map( ( item, i ) => (
67 <tr key={ i }>
68 <td>{ item.toUpperCase() }</td>
69 <td>
70 { humanFileSize(
71 stats.sizes[ item ].bytes
72 ) }{ ' ' }
73 ( { stats.sizes[ item ].percent }% )
74 </td>
75 </tr>
76 ) ) }
77 </tbody>
78 </table>
79 </div>
80 );
81 }
82
83 /**
84 * Fetch image data. If image is Smushing, update in 3 seconds.
85 *
86 * TODO: this could be optimized not to query so much.
87 *
88 * @param {Object} props
89 */
90 export function fetchProps( props ) {
91 const image = new wp.api.models.Media( { id: props.attributes.id } ),
92 smushData = props.attributes.smush;
93
94 image.fetch( { attribute: 'smush' } ).done( function( img ) {
95 if ( 'string' === typeof img.smush ) {
96 props.setAttributes( { smush: img.smush } );
97 //setTimeout( () => fetch( props ), 3000 );
98 } else if (
99 'undefined' !== typeof img.smush &&
100 ( 'undefined' === typeof smushData ||
101 JSON.stringify( smushData ) !== JSON.stringify( img.smush ) )
102 ) {
103 props.setAttributes( { smush: img.smush } );
104 }
105 } );
106 }
107
108 /**
109 * Modify the block’s edit component.
110 * Receives the original block BlockEdit component and returns a new wrapped component.
111 */
112 const smushStatsControl = createHigherOrderComponent( ( BlockEdit ) => {
113 return ( props ) => {
114 // If not image block or not selected, return unmodified block.
115 if (
116 'core/image' !== props.name ||
117 ! props.isSelected ||
118 'undefined' === typeof props.attributes.id
119 ) {
120 return (
121 <Fragment>
122 <BlockEdit { ...props } />
123 </Fragment>
124 );
125 }
126
127 const smushData = props.attributes.smush;
128 fetchProps( props );
129
130 return (
131 <Fragment>
132 <BlockEdit { ...props } />
133 <InspectorControls>
134 <PanelBody title={ window.smush_vars.strings.gb.stats }>
135 { smushStats( props.attributes.id, smushData ) }
136 </PanelBody>
137 </InspectorControls>
138 </Fragment>
139 );
140 };
141 }, 'withInspectorControl' );
142
143 wp.hooks.addFilter(
144 'editor.BlockEdit',
145 'wp-smush/smush-data-control',
146 smushStatsControl
147 );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * CDN functionality.
6 *
7 * @since 3.0
8 */
9 ( function() {
10 'use strict';
11
12 WP_Smush.CDN = {
13 cdnEnableButton: document.getElementById( 'smush-enable-cdn' ),
14 cdnDisableButton: document.getElementById( 'smush-cancel-cdn' ),
15 cdnStatsBox: document.querySelector( '.smush-cdn-stats' ),
16
17 init() {
18 /**
19 * Handle "Get Started" button click on disabled CDN page.
20 */
21 if ( this.cdnEnableButton ) {
22 this.cdnEnableButton.addEventListener( 'click', ( e ) => {
23 e.preventDefault();
24 e.currentTarget.classList.add( 'sui-button-onload' );
25 this.toggle_cdn( true );
26 } );
27 }
28
29 /**
30 * Handle "Deactivate' button click on CDN page.
31 */
32 if ( this.cdnDisableButton ) {
33 this.cdnDisableButton.addEventListener( 'click', ( e ) => {
34 e.preventDefault();
35 e.currentTarget.classList.add( 'sui-button-onload' );
36
37 this.toggle_cdn( false );
38 } );
39 }
40
41 this.updateStatsBox();
42 },
43
44 /**
45 * Toggle CDN.
46 *
47 * @since 3.0
48 *
49 * @param {boolean} enable
50 */
51 toggle_cdn( enable ) {
52 const nonceField = document.getElementsByName(
53 'wp_smush_options_nonce'
54 );
55
56 const xhr = new XMLHttpRequest();
57 xhr.open( 'POST', ajaxurl + '?action=smush_toggle_cdn', true );
58 xhr.setRequestHeader(
59 'Content-type',
60 'application/x-www-form-urlencoded'
61 );
62 xhr.onload = () => {
63 if ( 200 === xhr.status ) {
64 const res = JSON.parse( xhr.response );
65 if ( 'undefined' !== typeof res.success && res.success ) {
66 window.location.search = 'page=smush-cdn';
67 } else if ( 'undefined' !== typeof res.data.message ) {
68 WP_Smush.helpers.showErrorNotice( res.data.message );
69 }
70 } else {
71 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
72 }
73 };
74 xhr.send(
75 'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
76 );
77 },
78
79 /**
80 * Update the CDN stats box in summary meta box. Only fetch new data when on CDN page.
81 *
82 * @since 3.0
83 */
84 updateStatsBox() {
85 if (
86 'undefined' === typeof this.cdnStatsBox ||
87 ! this.cdnStatsBox
88 ) {
89 return;
90 }
91
92 // Only fetch the new stats, when user is on CDN page.
93 if ( ! window.location.search.includes( 'page=smush-cdn' ) ) {
94 return;
95 }
96
97 this.toggleElements();
98
99 const xhr = new XMLHttpRequest();
100 xhr.open( 'POST', ajaxurl + '?action=get_cdn_stats', true );
101 xhr.onload = () => {
102 if ( 200 === xhr.status ) {
103 const res = JSON.parse( xhr.response );
104 if ( 'undefined' !== typeof res.success && res.success ) {
105 this.toggleElements();
106 } else if ( 'undefined' !== typeof res.data.message ) {
107 WP_Smush.helpers.showErrorNotice( res.data.message );
108 }
109 } else {
110 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
111 }
112 };
113 xhr.send();
114 },
115
116 /**
117 * Show/hide elements during status update in the updateStatsBox()
118 *
119 * @since 3.1 Moved out from updateStatsBox()
120 */
121 toggleElements() {
122 const spinner = this.cdnStatsBox.querySelector(
123 '.sui-icon-loader'
124 );
125 const elements = this.cdnStatsBox.querySelectorAll(
126 '.wp-smush-stats > :not(.sui-icon-loader)'
127 );
128
129 for ( let i = 0; i < elements.length; i++ ) {
130 elements[ i ].classList.toggle( 'sui-hidden' );
131 }
132
133 spinner.classList.toggle( 'sui-hidden' );
134 },
135 };
136
137 WP_Smush.CDN.init();
138 } )();
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Directory scanner module that will Smush images in the Directory Smush modal.
6 *
7 * @since 2.8.1
8 *
9 * @param {string|number} totalSteps
10 * @param {string|number} currentStep
11 * @return {Object} Scan object.
12 * @class
13 */
14 const DirectoryScanner = ( totalSteps, currentStep ) => {
15 totalSteps = parseInt( totalSteps );
16 currentStep = parseInt( currentStep );
17
18 let cancelling = false,
19 failedItems = 0,
20 skippedItems = 0;
21
22 const obj = {
23 scan() {
24 const remainingSteps = totalSteps - currentStep;
25 if ( currentStep !== 0 ) {
26 // Scan started on a previous page load.
27 step( remainingSteps ).fail( this.showScanError );
28 } else {
29 jQuery
30 .post( ajaxurl, {
31 action: 'directory_smush_start',
32 _ajax_nonce: window.wp_smush_msgs.nonce
33 }, () =>
34 step( remainingSteps ).fail( this.showScanError )
35 )
36 .fail( this.showScanError );
37 }
38 },
39
40 cancel() {
41 cancelling = true;
42 return jQuery.post( ajaxurl, {
43 action: 'directory_smush_cancel',
44 _ajax_nonce: window.wp_smush_msgs.nonce
45 } );
46 },
47
48 getProgress() {
49 if ( cancelling ) {
50 return 0;
51 }
52 // O M G ... Logic at it's finest!
53 const remainingSteps = totalSteps - currentStep;
54 return Math.min(
55 Math.round(
56 ( parseInt( totalSteps - remainingSteps ) * 100 ) /
57 totalSteps
58 ),
59 99
60 );
61 },
62
63 onFinishStep( progress ) {
64 jQuery( '.wp-smush-progress-dialog .sui-progress-state-text' ).html(
65 currentStep -
66 failedItems +
67 '/' +
68 totalSteps +
69 ' ' +
70 window.wp_smush_msgs.progress_smushed
71 );
72 WP_Smush.directory.updateProgressBar( progress );
73 },
74
75 onFinish() {
76 WP_Smush.directory.updateProgressBar( 100 );
77 window.location.href =
78 window.wp_smush_msgs.directory_url + '&scan=done';
79 },
80
81 /**
82 * Displays an error when the scan request fails.
83 *
84 * @param {Object} res XHR object.
85 */
86 showScanError( res ) {
87 const dialog = jQuery( '#wp-smush-progress-dialog' );
88
89 // Add the error class to show/hide elements in the dialog.
90 dialog
91 .removeClass( 'wp-smush-exceed-limit' )
92 .addClass( 'wp-smush-scan-error' );
93
94 // Add the error status and description to the error message.
95 dialog
96 .find( '#smush-scan-error' )
97 .text( `${ res.status } ${ res.statusText }` );
98
99 // Show/hide the 403 error specific instructions.
100 const forbiddenMessage = dialog.find( '.smush-403-error-message' );
101 if ( 403 !== res.status ) {
102 forbiddenMessage.addClass( 'sui-hidden' );
103 } else {
104 forbiddenMessage.removeClass( 'sui-hidden' );
105 }
106 },
107
108 limitReached() {
109 const dialog = jQuery( '#wp-smush-progress-dialog' );
110
111 dialog.addClass( 'wp-smush-exceed-limit' );
112 dialog
113 .find( '#cancel-directory-smush' )
114 .attr( 'data-tooltip', window.wp_smush_msgs.bulk_resume );
115 dialog
116 .find( '.sui-box-body .sui-icon-close' )
117 .removeClass( 'sui-icon-close' )
118 .addClass( 'sui-icon-play' );
119 dialog
120 .find( '#cancel-directory-smush' )
121 .attr( 'id', 'cancel-directory-smush-disabled' );
122 },
123
124 resume() {
125 const dialog = jQuery( '#wp-smush-progress-dialog' );
126 const resume = dialog.find( '#cancel-directory-smush-disabled' );
127
128 dialog.removeClass( 'wp-smush-exceed-limit' );
129 dialog
130 .find( '.sui-box-body .sui-icon-play' )
131 .removeClass( 'sui-icon-play' )
132 .addClass( 'sui-icon-close' );
133 resume.attr( 'data-tooltip', 'Cancel' );
134 resume.attr( 'id', 'cancel-directory-smush' );
135
136 obj.scan();
137 },
138 };
139
140 /**
141 * Execute a scan step recursively
142 *
143 * Private to avoid overriding
144 *
145 * @param {number} remainingSteps
146 */
147 const step = function( remainingSteps ) {
148 if ( remainingSteps >= 0 ) {
149 currentStep = totalSteps - remainingSteps;
150 return jQuery.post(
151 ajaxurl,
152 {
153 action: 'directory_smush_check_step',
154 _ajax_nonce: window.wp_smush_msgs.nonce,
155 step: currentStep,
156 },
157 ( response ) => {
158 // We're good - continue on.
159 if (
160 'undefined' !== typeof response.success &&
161 response.success
162 ) {
163 if (
164 'undefined' !== typeof response.data &&
165 'undefined' !== typeof response.data.skipped &&
166 true === response.data.skipped
167 ) {
168 skippedItems++;
169 }
170
171 currentStep++;
172 remainingSteps = remainingSteps - 1;
173 obj.onFinishStep( obj.getProgress() );
174 step( remainingSteps ).fail( obj.showScanError );
175 } else if (
176 'undefined' !== typeof response.data.error &&
177 'dir_smush_limit_exceeded' === response.data.error
178 ) {
179 // Limit reached. Stop.
180 obj.limitReached();
181 } else {
182 // Error? never mind, continue, but count them.
183 failedItems++;
184 currentStep++;
185 remainingSteps = remainingSteps - 1;
186 obj.onFinishStep( obj.getProgress() );
187 step( remainingSteps ).fail( obj.showScanError );
188 }
189 }
190 );
191 }
192 return jQuery.post(
193 ajaxurl,
194 {
195 action: 'directory_smush_finish',
196 _ajax_nonce: window.wp_smush_msgs.nonce,
197 items: totalSteps - ( failedItems + skippedItems ),
198 failed: failedItems,
199 skipped: skippedItems,
200 },
201 ( response ) => obj.onFinish( response )
202 );
203 };
204
205 return obj;
206 };
207
208 export default DirectoryScanner;
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * Lazy loading functionality.
6 *
7 * @since 3.0
8 */
9 ( function() {
10 'use strict';
11
12 WP_Smush.Lazyload = {
13 lazyloadEnableButton: document.getElementById(
14 'smush-enable-lazyload'
15 ),
16 lazyloadDisableButton: document.getElementById(
17 'smush-cancel-lazyload'
18 ),
19
20 init() {
21 const self = this;
22
23 /**
24 * Handle "Activate" button click on disabled Lazy load page.
25 */
26 if ( this.lazyloadEnableButton ) {
27 this.lazyloadEnableButton.addEventListener( 'click', ( e ) => {
28 e.preventDefault();
29 e.currentTarget.classList.add( 'sui-button-onload' );
30
31 this.toggle_lazy_load( true );
32 } );
33 }
34
35 /**
36 * Handle "Deactivate' button click on Lazy load page.
37 */
38 if ( this.lazyloadDisableButton ) {
39 this.lazyloadDisableButton.addEventListener( 'click', ( e ) => {
40 e.preventDefault();
41 e.currentTarget.classList.add( 'sui-button-onload' );
42
43 this.toggle_lazy_load( false );
44 } );
45 }
46
47 /**
48 * Handle "Remove icon" button click on Lazy load page.
49 *
50 * This removes the image from the upload placeholder.
51 *
52 * @since 3.2.2
53 */
54 const removeSpinner = document.getElementById(
55 'smush-remove-spinner'
56 );
57 if ( removeSpinner ) {
58 removeSpinner.addEventListener( 'click', ( e ) => {
59 e.preventDefault();
60 this.removeLoaderIcon();
61 } );
62 }
63 const removePlaceholder = document.getElementById(
64 'smush-remove-placeholder'
65 );
66 if ( removePlaceholder ) {
67 removePlaceholder.addEventListener( 'click', ( e ) => {
68 e.preventDefault();
69 this.removeLoaderIcon( 'placeholder' );
70 } );
71 }
72
73 /**
74 * Handle "Remove" icon click.
75 *
76 * This removes the select icon from the list (not same as above functions).
77 *
78 * @since 3.2.2
79 */
80 const items = document.querySelectorAll( '.smush-ll-remove' );
81 if ( items && 0 < items.length ) {
82 items.forEach( function( el ) {
83 el.addEventListener( 'click', ( e ) => {
84 e.preventDefault();
85 e.target.closest( 'li' ).style.display = 'none';
86 self.remove(
87 e.target.dataset.id,
88 e.target.dataset.type
89 );
90 } );
91 } );
92 }
93
94 this.handlePredefinedPlaceholders();
95 },
96
97 /**
98 * Handle background color changes for the two predefined placeholders.
99 *
100 * @since 3.7.1
101 */
102 handlePredefinedPlaceholders() {
103 const pl1 = document.getElementById( 'placeholder-icon-1' );
104 if ( pl1 ) {
105 pl1.addEventListener( 'click', () => this.changeColor( '#F3F3F3' ) );
106 }
107
108 const pl2 = document.getElementById( 'placeholder-icon-2' );
109 if ( pl2 ) {
110 pl2.addEventListener( 'click', () => this.changeColor( '#333333' ) );
111 }
112 },
113
114 /**
115 * Set color.
116 *
117 * @since 3.7.1
118 * @param {string} color
119 */
120 changeColor( color ) {
121 document.getElementById( 'smush-color-picker' ).value = color;
122 document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > span > span' ).style.backgroundColor = color;
123 document.querySelector( '.sui-colorpicker-hex .sui-colorpicker-value > input' ).value = color;
124 },
125
126 /**
127 * Toggle lazy loading.
128 *
129 * @since 3.2.0
130 *
131 * @param {string} enable
132 */
133 toggle_lazy_load( enable ) {
134 const nonceField = document.getElementsByName(
135 'wp_smush_options_nonce'
136 );
137
138 const xhr = new XMLHttpRequest();
139 xhr.open(
140 'POST',
141 ajaxurl + '?action=smush_toggle_lazy_load',
142 true
143 );
144 xhr.setRequestHeader(
145 'Content-type',
146 'application/x-www-form-urlencoded'
147 );
148 xhr.onload = () => {
149 if ( 200 === xhr.status ) {
150 const res = JSON.parse( xhr.response );
151 if ( 'undefined' !== typeof res.success && res.success ) {
152 window.location.search = 'page=smush-lazy-load';
153 } else if ( 'undefined' !== typeof res.data.message ) {
154 WP_Smush.helpers.showErrorNotice( res.data.message );
155 document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
156 }
157 } else {
158 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
159 document.querySelector( '.sui-button-onload' ).classList.remove( 'sui-button-onload' );
160 }
161 };
162 xhr.send(
163 'param=' + enable + '&_ajax_nonce=' + nonceField[ 0 ].value
164 );
165 },
166
167 /**
168 * Add lazy load spinner icon.
169 *
170 * @since 3.2.2
171 * @param {string} type Accepts: spinner, placeholder.
172 */
173 addLoaderIcon( type = 'spinner' ) {
174 let frame;
175
176 // If the media frame already exists, reopen it.
177 if ( frame ) {
178 frame.open();
179 return;
180 }
181
182 // Create a new media frame
183 frame = wp.media( {
184 title: 'Select or upload an icon',
185 button: {
186 text: 'Select icon',
187 },
188 multiple: false, // Set to true to allow multiple files to be selected
189 } );
190
191 // When an image is selected in the media frame...
192 frame.on( 'select', function() {
193 // Get media attachment details from the frame state
194 const attachment = frame
195 .state()
196 .get( 'selection' )
197 .first()
198 .toJSON();
199
200 // Send the attachment URL to our custom image input field.
201 const imageIcon = document.getElementById(
202 'smush-' + type + '-icon-preview'
203 );
204 imageIcon.style.backgroundImage =
205 'url("' + attachment.url + '")';
206 imageIcon.style.display = 'block';
207
208 // Send the attachment id to our hidden input
209 document
210 .getElementById( 'smush-' + type + '-icon-file' )
211 .setAttribute( 'value', attachment.id );
212
213 // Hide the add image link
214 document.getElementById(
215 'smush-upload-' + type
216 ).style.display = 'none';
217
218 // Unhide the remove image link
219 const removeDiv = document.getElementById(
220 'smush-remove-' + type
221 );
222 removeDiv.querySelector( 'span' ).innerHTML =
223 attachment.filename;
224 removeDiv.style.display = 'block';
225 } );
226
227 // Finally, open the modal on click
228 frame.open();
229 },
230
231 /**
232 * Remove lazy load spinner icon.
233 *
234 * @since 3.2.2
235 * @param {string} type Accepts: spinner, placeholder.
236 */
237 removeLoaderIcon: ( type = 'spinner' ) => {
238 // Clear out the preview image
239 const imageIcon = document.getElementById(
240 'smush-' + type + '-icon-preview'
241 );
242 imageIcon.style.backgroundImage = '';
243 imageIcon.style.display = 'none';
244
245 // Un-hide the add image link
246 document.getElementById( 'smush-upload-' + type ).style.display =
247 'block';
248
249 // Hide the delete image link
250 document.getElementById( 'smush-remove-' + type ).style.display =
251 'none';
252
253 // Delete the image id from the hidden input
254 document
255 .getElementById( 'smush-' + type + '-icon-file' )
256 .setAttribute( 'value', '' );
257 },
258
259 /**
260 * Remove item.
261 *
262 * @param {number} id Image ID.
263 * @param {string} type Accepts: spinner, placeholder.
264 */
265 remove: ( id, type = 'spinner' ) => {
266 const nonceField = document.getElementsByName(
267 'wp_smush_options_nonce'
268 );
269 const xhr = new XMLHttpRequest();
270 xhr.open( 'POST', ajaxurl + '?action=smush_remove_icon', true );
271 xhr.setRequestHeader(
272 'Content-type',
273 'application/x-www-form-urlencoded'
274 );
275 xhr.send(
276 'id=' +
277 id +
278 '&type=' +
279 type +
280 '&_ajax_nonce=' +
281 nonceField[ 0 ].value
282 );
283 },
284 };
285
286 WP_Smush.Lazyload.init();
287 } )();
1 /* global smush_vars */
2 /* global _ */
3
4 /**
5 * Adds a Smush Now button and displays stats in Media Attachment Details Screen
6 */
7 (function ($, _) {
8 'use strict';
9
10 // Local reference to the WordPress media namespace.
11 const smushMedia = wp.media,
12 sharedTemplate =
13 "<span class='setting smush-stats' data-setting='smush'>" +
14 "<span class='name'><%= label %></span>" +
15 "<span class='value'><%= value %></span>" +
16 '</span>',
17 template = _.template(sharedTemplate);
18
19 /**
20 * Create the template.
21 *
22 * @param {string} smushHTML
23 * @return {Object} Template object
24 */
25 const prepareTemplate = function (smushHTML) {
26 /**
27 * @param {Array} smush_vars.strings Localization strings.
28 * @param {Object} smush_vars Object from wp_localize_script()
29 */
30 return template({
31 label: smush_vars.strings.stats_label,
32 value: smushHTML,
33 });
34 };
35
36 if (
37 'undefined' !== typeof smushMedia.view &&
38 'undefined' !== typeof smushMedia.view.Attachment.Details.TwoColumn
39 ) {
40 // Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
41 const smushMediaTwoColumn =
42 smushMedia.view.Attachment.Details.TwoColumn;
43
44 /**
45 * Add Smush details to attachment.
46 *
47 * A similar view to media.view.Attachment.Details
48 * for use in the Edit Attachment modal.
49 *
50 * @see wp-includes/js/media-grid.js
51 */
52 smushMedia.view.Attachment.Details.TwoColumn = smushMediaTwoColumn.extend(
53 {
54 initialize() {
55 smushMediaTwoColumn.prototype.initialize.apply(this, arguments);
56 this.listenTo(this.model, 'change:smush', this.render);
57 },
58
59 render() {
60 // Ensure that the main attachment fields are rendered.
61 smushMedia.view.Attachment.prototype.render.apply(
62 this,
63 arguments
64 );
65
66 const smushHTML = this.model.get('smush');
67 if (typeof smushHTML === 'undefined') {
68 return this;
69 }
70
71 this.model.fetch();
72
73 /**
74 * Detach the views, append our custom fields, make sure that our data is fully updated
75 * and re-render the updated view.
76 */
77 this.views.detach();
78 this.$el
79 .find('.settings')
80 .append(prepareTemplate(smushHTML));
81 this.views.render();
82
83 return this;
84 },
85 }
86 );
87 }
88
89 // Local instance of the Attachment Details TwoColumn used in the edit attachment modal view
90 const smushAttachmentDetails = smushMedia.view.Attachment.Details;
91
92 /**
93 * Add Smush details to attachment.
94 */
95 smushMedia.view.Attachment.Details = smushAttachmentDetails.extend({
96 initialize() {
97 smushAttachmentDetails.prototype.initialize.apply(this, arguments);
98 this.listenTo(this.model, 'change:smush', this.render);
99 },
100
101 render() {
102 // Ensure that the main attachment fields are rendered.
103 smushMedia.view.Attachment.prototype.render.apply(this, arguments);
104
105 const smushHTML = this.model.get('smush');
106 if (typeof smushHTML === 'undefined') {
107 return this;
108 }
109
110 this.model.fetch();
111
112 /**
113 * Detach the views, append our custom fields, make sure that our data is fully updated
114 * and re-render the updated view.
115 */
116 this.views.detach();
117 this.$el.append(prepareTemplate(smushHTML));
118
119 return this;
120 },
121 });
122
123 /**
124 * Create a new MediaLibraryTaxonomyFilter we later will instantiate
125 *
126 * @since 3.0
127 */
128 const MediaLibraryTaxonomyFilter = wp.media.view.AttachmentFilters.extend({
129 id: 'media-attachment-smush-filter',
130
131 createFilters() {
132 this.filters = {
133 all: {
134 text: smush_vars.strings.filter_all,
135 props: { stats: 'all' },
136 priority: 10,
137 },
138
139 unsmushed: {
140 text: smush_vars.strings.filter_not_processed,
141 props: { stats: 'unsmushed' },
142 priority: 20,
143 },
144
145 excluded: {
146 text: smush_vars.strings.filter_excl,
147 props: { stats: 'excluded' },
148 priority: 30,
149 },
150
151 failed: {
152 text: smush_vars.strings.filter_failed,
153 props: { stats: 'failed_processing' },
154 priority: 40,
155 },
156 };
157 },
158 });
159
160 /**
161 * Extend and override wp.media.view.AttachmentsBrowser to include our new filter.
162 *
163 * @since 3.0
164 */
165 const AttachmentsBrowser = wp.media.view.AttachmentsBrowser;
166 wp.media.view.AttachmentsBrowser = wp.media.view.AttachmentsBrowser.extend({
167 createToolbar() {
168 // Make sure to load the original toolbar
169 AttachmentsBrowser.prototype.createToolbar.call(this);
170 this.toolbar.set(
171 'MediaLibraryTaxonomyFilter',
172 new MediaLibraryTaxonomyFilter({
173 controller: this.controller,
174 model: this.collection.props,
175 priority: -75,
176 }).render()
177 );
178 },
179 });
180 })(jQuery, _);
1 /* global ajaxurl */
2 /* global wp_smush_msgs */
3 /* global WP_Smush */
4 /* global SUI */
5
6 ( function( $ ) {
7 'use strict';
8
9 /**
10 * Bulk compress page.
11 */
12 $( 'form#smush-bulk-form' ).on( 'submit', function( e ) {
13 e.preventDefault();
14 $( '#save-settings-button' ).addClass( 'sui-button-onload' );
15 saveSettings( $( this ).serialize(), 'bulk' );
16 // runReCheck();
17 } );
18
19 /**
20 * Lazy load page.
21 */
22 $( 'form#smush-lazy-load-form' ).on( 'submit', function( e ) {
23 e.preventDefault();
24 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
25 saveSettings( $( this ).serialize(), 'lazy-load' );
26 } );
27
28 /**
29 * CDN page.
30 */
31 $( 'form#smush-cdn-form' ).on( 'submit', function( e ) {
32 e.preventDefault();
33 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
34 saveSettings( $( this ).serialize(), 'cdn' );
35 } );
36
37 /**
38 * Integrations page.
39 */
40 $( 'form#smush-integrations-form' ).on( 'submit', function( e ) {
41 e.preventDefault();
42 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
43 saveSettings( $( this ).serialize(), 'integrations' );
44 } );
45
46 /**
47 * Settings page.
48 */
49 $( 'form#smush-settings-form' ).on( 'submit', function( e ) {
50 e.preventDefault();
51 $( '#save-settings-button' ).addClass( 'sui-button-onload-text' );
52 saveSettings( $( this ).serialize(), 'settings' );
53 } );
54
55 /**
56 * Save settings.
57 *
58 * @param {string} settings JSON string of settings.
59 * @param {string} page Settings page.
60 */
61 function saveSettings( settings, page ) {
62 const xhr = new XMLHttpRequest();
63
64 xhr.open( 'POST', ajaxurl + '?action=smush_save_settings', true );
65 xhr.setRequestHeader(
66 'Content-type',
67 'application/x-www-form-urlencoded'
68 );
69
70 xhr.onload = () => {
71 $( '#save-settings-button' ).removeClass(
72 'sui-button-onload-text sui-button-onload'
73 );
74
75 if ( 200 === xhr.status ) {
76 const res = JSON.parse( xhr.response );
77 if ( 'undefined' !== typeof res.success && res.success ) {
78 showSuccessNotice( wp_smush_msgs.settingsUpdated );
79 triggerSavedSmushSettingsEvent( res.data );
80 } else if ( res.data && res.data.message ) {
81 WP_Smush.helpers.showErrorNotice( res.data.message );
82 } else {
83 WP_Smush.helpers.showErrorNotice( 'Request failed.' );
84 }
85 } else {
86 WP_Smush.helpers.showErrorNotice( 'Request failed. Returned status of ' + xhr.status );
87 }
88 };
89
90 xhr.send( 'page=' + page + '&' + settings + '&_ajax_nonce=' + wp_smush_msgs.nonce );
91 }
92
93 function triggerSavedSmushSettingsEvent( status ) {
94 document.dispatchEvent(
95 new CustomEvent( 'onSavedSmushSettings', {
96 detail: status
97 } )
98 );
99 }
100
101 /**
102 * Show successful update notice.
103 *
104 * @param {string} msg Notice message.
105 */
106 function showSuccessNotice( msg ) {
107 const noticeMessage = `<p>${ msg }</p>`,
108 noticeOptions = {
109 type: 'success',
110 icon: 'check',
111 };
112
113 SUI.openNotice( 'wp-smush-ajax-notice', noticeMessage, noticeOptions );
114
115 const loadingButton = document.querySelector( '.sui-button-onload' );
116 if ( loadingButton ) {
117 loadingButton.classList.remove( 'sui-button-onload' );
118 }
119 }
120
121 /**
122 * Re-check images from bulk smush and integrations pages.
123 */
124 function runReCheck() {
125 $( '#save-settings-button' ).addClass( 'sui-button-onload' );
126
127 const param = {
128 action: 'scan_for_resmush',
129 wp_smush_options_nonce: $( '#wp_smush_options_nonce' ).val(),
130 type: 'media',
131 };
132
133 // Send ajax, Update Settings, And Check For resmush.
134 $.post( ajaxurl, $.param( param ) ).done( function() {
135 $( '#save-settings-button' ).removeClass( 'sui-button-onload' );
136 } );
137 }
138
139 /**
140 * Parse remove data change.
141 */
142 $( 'input[name=keep_data]' ).on( 'change', function( e ) {
143 const otherClass =
144 'keep_data-true' === e.target.id
145 ? 'keep_data-false'
146 : 'keep_data-true';
147 e.target.parentNode.classList.add( 'active' );
148 document
149 .getElementById( otherClass )
150 .parentNode.classList.remove( 'active' );
151 } );
152
153 /**
154 * Handle auto-detect checkbox toggle, to show/hide highlighting notice.
155 */
156 $( 'input#detection' ).on( 'click', function() {
157 const noticeDiv = $( '.smush-highlighting-notice' );
158 const warningDiv = $( '.smush-highlighting-warning' );
159
160 // Setting enabled.
161 if ( $( this ).is( ':checked' ) ) {
162 // Highlighting is already active and setting not saved.
163 if ( noticeDiv.length > 0 ) {
164 noticeDiv.show();
165 } else {
166 warningDiv.show();
167 }
168 } else {
169 noticeDiv.hide();
170 warningDiv.hide();
171 }
172 } );
173 }( jQuery ) );
1 /* global WP_Smush */
2 /* global ajaxurl */
3
4 /**
5 * WebP functionality.
6 *
7 * @since 3.8.0
8 */
9
10 (function () {
11 'use strict';
12
13 WP_Smush.WebP = {
14 nonceField: document.getElementsByName('wp_smush_options_nonce'),
15 toggleModuleButton: document.getElementById('smush-toggle-webp-button'),
16 recheckStatusButton: document.getElementById('smush-webp-recheck'),
17 recheckStatusLink: document.getElementById('smush-webp-recheck-link'),
18 showWizardButton: document.getElementById('smush-webp-toggle-wizard'),
19
20 init() {
21 this.maybeShowDeleteAllSuccessNotice();
22
23 /**
24 * Handles the "Deactivate" and "Get Started" buttons on the WebP page.
25 */
26 if (this.toggleModuleButton) {
27 this.toggleModuleButton.addEventListener('click', (e) =>
28 this.toggleWebp(e)
29 );
30 }
31
32 /**
33 * Handle "RE-CHECK STATUS' button click on WebP page.
34 */
35 if (this.recheckStatusButton) {
36 this.recheckStatusButton.addEventListener('click', (e) => {
37 e.preventDefault();
38 this.recheckStatus();
39 });
40 }
41
42 /**
43 * Handle "RE-CHECK STATUS' link click on WebP page.
44 */
45 if (this.recheckStatusLink) {
46 this.recheckStatusLink.addEventListener('click', (e) => {
47 e.preventDefault();
48 this.recheckStatus();
49 });
50 }
51
52 /**
53 * Handles the "Delete WebP images" button.
54 */
55 if (document.getElementById('wp-smush-webp-delete-all')) {
56 document
57 .getElementById('wp-smush-webp-delete-all')
58 .addEventListener('click', (e) => this.deleteAll(e));
59 }
60
61 if (this.showWizardButton) {
62 this.showWizardButton.addEventListener(
63 'click',
64 this.toggleWizard
65 );
66 }
67 },
68
69 /**
70 * Toggle WebP module.
71 *
72 * @param {Event} e
73 */
74 toggleWebp(e) {
75 e.preventDefault();
76
77 const button = e.currentTarget,
78 doEnable = 'enable' === button.dataset.action;
79
80 button.classList.add('sui-button-onload');
81
82 const xhr = new XMLHttpRequest();
83 xhr.open('POST', ajaxurl + '?action=smush_webp_toggle', true);
84 xhr.setRequestHeader(
85 'Content-type',
86 'application/x-www-form-urlencoded'
87 );
88
89 xhr.onload = () => {
90 const res = JSON.parse(xhr.response);
91
92 if (200 === xhr.status) {
93 if ('undefined' !== typeof res.success && res.success) {
94 const scanPromise = this.runScan();
95 scanPromise.onload = () => {
96 window.location.href =
97 window.wp_smush_msgs.localWebpURL;
98 };
99 } else if ('undefined' !== typeof res.data.message) {
100 this.showNotice(res.data.message);
101 button.classList.remove('sui-button-onload');
102 }
103 } else {
104 let message = window.wp_smush_msgs.generic_ajax_error;
105 if (res && 'undefined' !== typeof res.data.message) {
106 message = res.data.message;
107 }
108 this.showNotice(message);
109 button.classList.remove('sui-button-onload');
110 }
111 };
112
113 xhr.send(
114 'param=' + doEnable + '&_ajax_nonce=' + this.nonceField[0].value
115 );
116 },
117
118 /**
119 * re-check server configuration for WebP.
120 */
121 recheckStatus() {
122 this.recheckStatusButton.classList.add('sui-button-onload');
123
124 const xhr = new XMLHttpRequest();
125 xhr.open('POST', ajaxurl + '?action=smush_webp_get_status', true);
126 xhr.setRequestHeader(
127 'Content-type',
128 'application/x-www-form-urlencoded'
129 );
130 xhr.onload = () => {
131 this.recheckStatusButton.classList.remove('sui-button-onload');
132 let message = false;
133 const res = JSON.parse(xhr.response);
134 if (200 === xhr.status) {
135 const isConfigured = res.success ? '1' : '0';
136 if (
137 isConfigured !==
138 this.recheckStatusButton.dataset.isConfigured
139 ) {
140 // Reload the page when the configuration status changed.
141 location.reload();
142 }
143 } else {
144 message = window.wp_smush_msgs.generic_ajax_error;
145 }
146
147 if (res && res.data) {
148 message = res.data;
149 }
150
151 if (message) {
152 this.showNotice(message);
153 }
154 };
155 xhr.send('_ajax_nonce=' + window.wp_smush_msgs.webp_nonce);
156 },
157
158 deleteAll(e) {
159 const button = e.currentTarget;
160 button.classList.add('sui-button-onload');
161
162 let message = false;
163 const xhr = new XMLHttpRequest();
164 xhr.open('POST', ajaxurl + '?action=smush_webp_delete_all', true);
165 xhr.setRequestHeader(
166 'Content-type',
167 'application/x-www-form-urlencoded'
168 );
169
170 xhr.onload = () => {
171 const res = JSON.parse(xhr.response);
172 if (200 === xhr.status) {
173 if ('undefined' !== typeof res.success && res.success) {
174 const scanPromise = this.runScan();
175 scanPromise.onload = () => {
176 location.search =
177 location.search + '&notice=webp-deleted';
178 };
179 } else {
180 message = window.wp_smush_msgs.generic_ajax_error;
181 }
182 } else {
183 message = window.wp_smush_msgs.generic_ajax_error;
184 }
185
186 if (res && res.data && res.data.message) {
187 message = res.data.message;
188 }
189
190 if (message) {
191 button.classList.remove('sui-button-onload');
192
193 const noticeMessage = `<p style="text-align: left;">${message}</p>`;
194 const noticeOptions = {
195 type: 'error',
196 icon: 'info',
197 autoclose: {
198 show: false,
199 },
200 };
201
202 window.SUI.openNotice(
203 'wp-smush-webp-delete-all-error-notice',
204 noticeMessage,
205 noticeOptions
206 );
207 }
208 };
209
210 xhr.send('_ajax_nonce=' + this.nonceField[0].value);
211 },
212
213 toggleWizard(e) {
214 e.currentTarget.classList.add('sui-button-onload');
215
216 const xhr = new XMLHttpRequest();
217 xhr.open(
218 'GET',
219 ajaxurl +
220 '?action=smush_toggle_webp_wizard&_ajax_nonce=' +
221 window.wp_smush_msgs.webp_nonce,
222 true
223 );
224 xhr.onload = () => location.reload();
225 xhr.send();
226 },
227
228 /**
229 * Triggers the scanning of images for updating the images to re-smush.
230 *
231 * @since 3.8.0
232 */
233 runScan() {
234 const xhr = new XMLHttpRequest(),
235 nonceField = document.getElementsByName(
236 'wp_smush_options_nonce'
237 );
238
239 xhr.open('POST', ajaxurl + '?action=scan_for_resmush', true);
240 xhr.setRequestHeader(
241 'Content-type',
242 'application/x-www-form-urlencoded'
243 );
244
245 xhr.send('_ajax_nonce=' + nonceField[0].value);
246
247 return xhr;
248 },
249
250 /**
251 * Show message (notice).
252 *
253 * @param {string} message
254 * @param {string} type
255 */
256 showNotice(message, type) {
257 if ('undefined' === typeof message) {
258 return;
259 }
260
261 const noticeMessage = `<p>${message}</p>`;
262 const noticeOptions = {
263 type: type || 'error',
264 icon: 'info',
265 dismiss: {
266 show: true,
267 label: window.wp_smush_msgs.noticeDismiss,
268 tooltip: window.wp_smush_msgs.noticeDismissTooltip,
269 },
270 autoclose: {
271 show: false,
272 },
273 };
274
275 window.SUI.openNotice(
276 'wp-smush-ajax-notice',
277 noticeMessage,
278 noticeOptions
279 );
280 },
281
282 /**
283 * Show delete all webp success notice.
284 */
285 maybeShowDeleteAllSuccessNotice() {
286 if (!document.getElementById('wp-smush-webp-delete-all-notice')) {
287 return;
288 }
289 const noticeMessage = `<p>${
290 document.getElementById('wp-smush-webp-delete-all-notice')
291 .dataset.message
292 }</p>`;
293
294 const noticeOptions = {
295 type: 'success',
296 icon: 'check-tick',
297 dismiss: {
298 show: true,
299 },
300 };
301
302 window.SUI.openNotice(
303 'wp-smush-webp-delete-all-notice',
304 noticeMessage,
305 noticeOptions
306 );
307 },
308 };
309
310 WP_Smush.WebP.init();
311 })();
1 /* global ajaxurl */
2
3 /**
4 * External dependencies
5 */
6 import React from 'react';
7 import ReactDOM from 'react-dom';
8
9 /**
10 * WordPress dependencies
11 */
12 import domReady from '@wordpress/dom-ready';
13
14 /**
15 * SUI dependencies
16 */
17 import { TutorialsList, TutorialsSlider } from '@wpmudev/shared-tutorials';
18
19 import MixPanel from "./mixpanel"
20
21 function hideTutorials() {
22 const xhr = new XMLHttpRequest();
23
24 xhr.open( 'POST', ajaxurl + '?action=smush_hide_tutorials', true );
25 xhr.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' );
26
27 xhr.onload = () => {
28 if ( 200 === xhr.status ) {
29 const noticeMessage = `<p>${ window.wp_smush_msgs.tutorialsRemoved }</p>`,
30 noticeOptions = {
31 type: 'success',
32 icon: 'check',
33 };
34
35 window.SUI.openNotice(
36 'wp-smush-ajax-notice',
37 noticeMessage,
38 noticeOptions
39 );
40 }
41 };
42
43 xhr.send( '_ajax_nonce=' + window.wp_smush_msgs.nonce );
44 }
45
46 /**
47 * Render the "Tutorials List" component.
48 *
49 * @since 2.8.5
50 */
51 domReady( function() {
52 // Tutorials section on Dashboard page.
53 const tutorialsDiv = document.getElementById( 'smush-dash-tutorials' );
54 if ( tutorialsDiv ) {
55 ReactDOM.render(
56 <TutorialsSlider
57 category="11228"
58 title={ window.smush_tutorials.tutorials }
59 viewAll={ window.smush_tutorials.tutorials_link }
60 onCloseClick={ hideTutorials }
61 />,
62 tutorialsDiv
63 );
64 }
65
66 // Tutorials page.
67 const tutorialsPageBox = document.getElementById( 'smush-box-tutorials' );
68 if ( tutorialsPageBox ) {
69 ReactDOM.render(
70 <TutorialsList
71 category="11228"
72 title={ window.smush_tutorials.tutorials }
73 translate={ window.smush_tutorials.tutorials_strings }
74 />,
75 tutorialsPageBox
76 );
77 }
78 } );
79
80 jQuery(function ($) {
81 $(document).on('click', '#smush-box-tutorials li > [role="link"], #smush-dash-tutorials li > [role="link"]', function () {
82 const $tutorial = $(this);
83 const isDashPage = !!$tutorial.closest('#smush-dash-tutorials').length;
84 const decodeHtml = (html) => {
85 const txt = document.createElement("textarea");
86 txt.innerHTML = html;
87 return txt.value;
88 };
89 const title = decodeHtml($tutorial.attr('title'));
90
91 (new MixPanel()).track('Tutorial Opened', {
92 'Tutorial Name': title,
93 'Triggered From': isDashPage ? 'Dashboard' : 'Tutorials Tab'
94 });
95 });
96 });
1 /* global ajaxurl */
2
3 /**
4 * External dependencies
5 */
6 import assign from 'lodash/assign';
7
8 /**
9 * Wrapper function for ajax calls to WordPress.
10 *
11 * @since 3.12.0
12 */
13 function SmushFetcher() {
14 /**
15 * Request ajax with a promise.
16 * Use FormData Object as data if you need to upload file
17 *
18 * @param {string} action
19 * @param {Object|FormData} data
20 * @param {string} method
21 * @return {Promise<any>} Request results.
22 */
23 function request(action, data = {}, method = 'POST') {
24 const args = {
25 url: ajaxurl,
26 method,
27 cache: false
28 };
29
30 if (data instanceof FormData) {
31 data.append('action', action);
32 data.append('_ajax_nonce', window.wp_smush_msgs.nonce);
33 args.contentType = false;
34 args.processData = false;
35 } else {
36 data._ajax_nonce = data._ajax_nonce || window.wp_smush_msgs.nonce;
37 data.action = action;
38 }
39 args.data = data;
40 return new Promise((resolve, reject) => {
41 jQuery.ajax(args).done(resolve).fail(reject);
42 }).then((response) => {
43 if (typeof response !== 'object') {
44 response = JSON.parse(response);
45 }
46 return response;
47 }).catch((error) => {
48 console.error('Error:', error);
49 });
50 }
51
52 const methods = {
53 /**
54 * Manage ajax for background.
55 */
56 background: {
57 /**
58 * Start background process.
59 */
60 start: () => {
61 return request('bulk_smush_start');
62 },
63
64 /**
65 * Cancel background process.
66 */
67 cancel: () => {
68 return request('bulk_smush_cancel');
69 },
70
71 /**
72 * Initial State - Get stats on the first time.
73 */
74 initState: () => {
75 return request('bulk_smush_get_status');
76 },
77
78 /**
79 * Get stats.
80 */
81 getStatus: () => {
82 return request('bulk_smush_get_status');
83 },
84
85 getStats: () => {
86 return request('bulk_smush_get_global_stats');
87 }
88 },
89 smush: {
90 /**
91 * Sync stats.
92 */
93 syncStats: ( data ) => {
94 data = data || {};
95 return request('get_stats', data);
96 },
97
98 /**
99 * Ignore All.
100 */
101 ignoreAll: ( type ) => {
102 return request('wp_smush_ignore_all_failed_items', {
103 type: type,
104 });
105 },
106 },
107
108 /**
109 * Manage ajax for other requests
110 */
111 common: {
112 /**
113 * Dismiss Notice.
114 *
115 * @param {string} dismissId Notification id.
116 */
117 dismissNotice: (dismissId) => {
118 return request('smush_dismiss_notice', {
119 key: dismissId
120 });
121 },
122
123 /**
124 * Hide the new features modal.
125 *
126 * @param {string} modalID Notification id.
127 */
128 hideModal: (modalID) => request('hide_modal', {
129 modal_id: modalID,
130 }),
131
132 /**
133 * Custom request.
134 *
135 * @param {Object} data
136 */
137 request: (data) => data.action && request(data.action, data),
138 },
139
140 scanMediaLibrary: {
141 start: ( optimize_on_scan_completed = false ) => {
142 optimize_on_scan_completed = optimize_on_scan_completed ? 1 : 0;
143 const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
144 return request( 'wp_smush_start_background_scan', {
145 optimize_on_scan_completed,
146 _ajax_nonce,
147 } );
148 },
149
150 cancel: () => {
151 const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
152 return request( 'wp_smush_cancel_background_scan', {
153 _ajax_nonce,
154 } );
155 },
156
157 getScanStatus: () => {
158 const _ajax_nonce = window.wp_smushit_data.media_library_scan.nonce;
159 return request( 'wp_smush_get_background_scan_status', {
160 _ajax_nonce,
161 } );
162 },
163 },
164 };
165
166 assign(this, methods);
167 }
168
169 const SmushAjax = new SmushFetcher();
170 export default SmushAjax;
...\ No newline at end of file ...\ No newline at end of file
1 import React, {useEffect, useRef, useState} from "react";
2 import {post} from "../utils/request";
3 import MediaLibraryScannerModal from "./media-library-scanner-modal";
4
5 export default function AjaxMediaLibraryScannerModal(
6 {
7 nonce = '',
8 onScanCompleted = () => false,
9 onClose = () => false,
10 focusAfterClose = ''
11 }
12 ) {
13 const [inProgress, setInProgress] = useState(false);
14 const [progress, setProgress] = useState(0);
15 const [cancelled, setCancelled] = useState(false);
16 const cancelledRef = useRef();
17
18 useEffect(() => {
19 cancelledRef.current = cancelled;
20 }, [cancelled]);
21
22 function start() {
23 setInProgress(true);
24
25 post('wp_smush_before_scan_library', nonce).then((response) => {
26 const sliceCount = response?.slice_count;
27 const slicesList = range(sliceCount, 1);
28 const parallelRequests = response?.parallel_requests;
29
30 handleBatch(slicesList, sliceCount, parallelRequests)
31 .then(() => {
32 setTimeout(() => {
33 onScanCompleted();
34 }, 1000);
35 });
36 });
37 }
38
39 function handleBatch(remainingSlicesList, sliceCount, parallelRequests) {
40 const batchPromises = [];
41 const completedSliceCount = Math.max(sliceCount - remainingSlicesList.length, 0);
42 const batch = remainingSlicesList.splice(0, parallelRequests);
43
44 updateProgress(completedSliceCount, sliceCount);
45
46 batch.forEach((sliceNumber) => {
47 batchPromises.push(
48 post('wp_smush_scan_library_slice', nonce, {slice: sliceNumber})
49 );
50 });
51
52 return new Promise((resolve) => {
53 Promise.all(batchPromises)
54 .then(() => {
55 if (!cancelledRef.current) {
56 if (remainingSlicesList.length) {
57 handleBatch(remainingSlicesList, sliceCount, parallelRequests)
58 .then(resolve);
59 } else {
60 updateProgress(sliceCount, sliceCount);
61 resolve();
62 }
63 }
64 });
65 });
66 }
67
68 function range(size, startAt = 0) {
69 return [...Array(size).keys()].map(i => i + startAt);
70 }
71
72 function cancelScan() {
73 setCancelled(true);
74 setInProgress(false);
75 setProgress(0);
76 }
77
78 function updateProgress(completedSlices, totalSlices) {
79 const progress = (completedSlices / totalSlices) * 100;
80 setProgress(progress);
81 }
82
83 return <MediaLibraryScannerModal
84 inProgress={inProgress}
85 progress={progress}
86 onCancel={cancelScan}
87 focusAfterClose={focusAfterClose}
88 onClose={onClose}
89 onStart={start}
90 />;
91 };
1 import React, {useRef, useState} from "react";
2 import {post} from "../utils/request";
3 import MediaLibraryScannerModal from "./media-library-scanner-modal";
4
5 export default function BackgroundMediaLibraryScannerModal(
6 {
7 nonce = '',
8 onScanCompleted = () => false,
9 onClose = () => false,
10 focusAfterClose = ''
11 }
12 ) {
13 const [inProgress, setInProgress] = useState(false);
14 const [progress, setProgress] = useState(0);
15 const [cancelled, setCancelled] = useState(false);
16 const progressTimeoutId = useRef(0);
17
18 function start() {
19 post('wp_smush_start_background_scan', nonce).then(() => {
20 setInProgress(true);
21 progressTimeoutId.current = setTimeout(updateProgress, 2000);
22 });
23 }
24
25 function clearProgressTimeout() {
26 if (progressTimeoutId.current) {
27 clearTimeout(progressTimeoutId.current);
28 }
29 }
30
31 function updateProgress() {
32 post('wp_smush_get_background_scan_status', nonce).then(response => {
33 const isCompleted = response?.is_completed;
34 if (isCompleted) {
35 clearProgressTimeout();
36 onScanCompleted();
37 return;
38 }
39
40 const isCancelled = response?.is_cancelled;
41 if (isCancelled) {
42 clearProgressTimeout();
43 changeStateToCancelled();
44 return;
45 }
46
47 const totalItems = response?.total_items;
48 const processedItems = response?.processed_items;
49 const progress = (processedItems / totalItems) * 100;
50 setProgress(progress);
51
52 progressTimeoutId.current = setTimeout(updateProgress, 1000);
53 });
54 }
55
56 function cancelScan() {
57 clearProgressTimeout();
58 post('wp_smush_cancel_background_scan', nonce)
59 .then(changeStateToCancelled);
60 }
61
62 function changeStateToCancelled() {
63 setCancelled(true);
64 setProgress(0);
65 setInProgress(false);
66 }
67
68 return <MediaLibraryScannerModal
69 inProgress={inProgress}
70 progress={progress}
71 onCancel={cancelScan}
72 focusAfterClose={focusAfterClose}
73 onClose={onClose}
74 onStart={start}
75 />;
76 };
1 import React, {useEffect, useRef, useState} from "react";
2 import Modal from "../common/modal";
3 import {post} from "../utils/request";
4 import Button from "../common/button";
5 import ProgressBar from "../common/progress-bar";
6
7 const {__} = wp.i18n;
8
9 export default function MediaLibraryScannerModal(
10 {
11 inProgress = false,
12 progress = 0,
13 onClose = () => false,
14 onStart = () => false,
15 onCancel = () => false,
16 focusAfterClose = ''
17 }
18 ) {
19 function content() {
20 if (inProgress) {
21 return <>
22 <ProgressBar progress={progress}/>
23 <Button id="wp-smush-cancel-media-library-scan"
24 icon="sui-icon-close"
25 text={__('Cancel', 'wp-smushit')}
26 ghost={true}
27 onClick={onCancel}
28 />
29 </>;
30 } else {
31 return <>
32 <Button id="wp-smush-start-media-library-scan"
33 icon="sui-icon-play"
34 text={__('Start', 'wp-smushit')}
35 onClick={onStart}
36 />
37 </>;
38 }
39 }
40
41 return <Modal id="wp-smush-media-library-scanner-modal"
42 title={__('Scan Media Library', 'wp-smushit')}
43 description={__('Scans the media library to detect items to Smush.', 'wp-smushit')}
44 onClose={onClose}
45 focusAfterClose={focusAfterClose}
46 disableCloseButton={inProgress}>
47 {content()}
48 </Modal>;
49 };
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.