Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
82ee1fb
feat: add smart optimization to image instant optimization
Arukuen Mar 31, 2026
de8cfe0
fix: remove force arg since we now allow same format conversion for s…
Arukuen Apr 6, 2026
a13e346
Merge branch 'develop' into feat/35-image-smart-optimization
Arukuen Apr 7, 2026
c9f9905
fix: speedup smart optimization, add toggle in admin
Arukuen Apr 7, 2026
90b39a5
Merge branch 'develop' into feat/35-image-smart-optimization
Arukuen Apr 7, 2026
eef9cfb
fix: updated admin setting
bfintal Apr 7, 2026
d5413ac
fix: updated label when smart optimized
bfintal Apr 7, 2026
89c1cd7
fix: smart optimization should be false if on free
bfintal Apr 7, 2026
769e1d4
fix: smart optimization is off if free
bfintal Apr 7, 2026
63684ed
fix: add delayed progress for long optimization
Arukuen Apr 8, 2026
0b65bc6
Merge branch 'feat/35-image-smart-optimization' of https://github.com…
Arukuen Apr 8, 2026
975b741
fix: add progress to smart and non-smart
Arukuen Apr 10, 2026
8f87b2d
fix: use the right progress status
Arukuen Apr 10, 2026
4382fd4
chore: updated tested up to 7.0
bfintal Apr 10, 2026
36b5b00
Merge branch 'feat/35-image-smart-optimization' of https://github.com…
bfintal Apr 10, 2026
94a54a2
always close the progress modal even if it errors
bfintal Apr 20, 2026
8284887
added an indeterminate progress bar style
bfintal Apr 20, 2026
5defd2c
fix: change progress delay from 700ms to 1000ms
Arukuen Apr 22, 2026
90f8f3b
Merge branch 'develop' into feat/35-image-smart-optimization
Arukuen May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/admin/class-admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,9 @@ public function register_settings() {
],

// Image Optimization settings
'smart_optimization' => [
'type' => 'integer',
],
'webp_quality' => [
'type' => 'integer',
],
Expand Down Expand Up @@ -347,6 +350,11 @@ public function sanitize_options( $options ) {
$sanitized['thumbnail_sizes'] = array_map( 'sanitize_text_field', $options['thumbnail_sizes'] );
}

// Sanitize smart optimization
if ( isset( $options['smart_optimization'] ) ) {
$sanitized['smart_optimization'] = $options['smart_optimization'] ? 1 : 0;
}

// Sanitize webp quality
if ( isset( $options['webp_quality'] ) ) {
$quality = absint( $options['webp_quality'] );
Expand Down
19 changes: 14 additions & 5 deletions src/admin/class-meta-box.php
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,20 @@ function cimo_get_media_type_label( $mimetype ) {

// Converted format
echo '<li class="cimo-converted">';
echo '🏞️ ' . sprintf(
/* translators: %s: converted format */
esc_html__( 'Converted to %s', 'cimo-image-optimizer' ),
'<span class="cimo-value">' . esc_html( $converted_format ) . '</span>'
);
$converted_format_markup = '<span class="cimo-value">' . esc_html( $converted_format ) . '</span>';
if ( ! empty( $cimo['smartOptimized'] ) ) {
echo '🏞️ ' . sprintf(
/* translators: %s: converted format */
esc_html__( 'Smart optimized to %s', 'cimo-image-optimizer' ),
$converted_format_markup
);
} else {
echo '🏞️ ' . sprintf(
/* translators: %s: converted format */
esc_html__( 'Converted to %s', 'cimo-image-optimizer' ),
$converted_format_markup
);
}
echo '</li>';

// Conversion time
Expand Down
7 changes: 7 additions & 0 deletions src/admin/class-metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public function register_rest_route() {
'convertedFilesize',
'conversionTime',
'compressionSavings',
'smartOptimized',
];
if ( ! is_array( $value ) ) {
// translators: The %s is the parameter name.
Expand Down Expand Up @@ -85,6 +86,7 @@ public function register_rest_route() {
'convertedFilesize',
'conversionTime',
'compressionSavings',
'smartOptimized',
];
$sanitized = [];
if ( is_array( $value ) ) {
Expand All @@ -98,6 +100,8 @@ public function register_rest_route() {
$entry[ $key ] = intval( $item[ $key ] );
} elseif ( in_array( $key, [ 'conversionTime', 'compressionSavings' ], true ) ) {
$entry[ $key ] = floatval( $item[ $key ] );
} elseif ( $key === 'smartOptimized' ) {
$entry[ $key ] = ! empty( $item[ $key ] ) ? 1 : 0;
} else {
$entry[ $key ] = sanitize_text_field( $item[ $key ] );
}
Expand Down Expand Up @@ -137,6 +141,7 @@ public function save_metadata( $request ) {
'convertedFilesize',
'conversionTime',
'compressionSavings',
'smartOptimized',
];
$sanitized_metadata = [];
foreach ( $metadata_array as $item ) {
Expand All @@ -149,6 +154,8 @@ public function save_metadata( $request ) {
$entry[ $key ] = intval( $item[ $key ] );
} elseif ( in_array( $key, [ 'conversionTime', 'compressionSavings' ], true ) ) {
$entry[ $key ] = floatval( $item[ $key ] );
} elseif ( $key === 'smartOptimized' ) {
$entry[ $key ] = ! empty( $item[ $key ] ) ? 1 : 0;
} else {
$entry[ $key ] = sanitize_text_field( $item[ $key ] );
}
Expand Down
3 changes: 3 additions & 0 deletions src/admin/class-script-loader.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ public static function enqueue_cimo_assets() {
'showOptimizationToggle' => isset( $settings['show_optimization_toggle'] ) ? (int) $settings['show_optimization_toggle'] : 0,
'showOptimizationToggleFrontend' => isset( $settings['show_optimization_toggle_frontend'] ) ? (int) $settings['show_optimization_toggle_frontend'] : 0,
'persistOptimizationToggle' => isset( $settings['persist_optimization_toggle'] ) ? (int) $settings['persist_optimization_toggle'] : 0,
'smartOptimization' => CIMO_BUILD === 'premium'
? ( isset( $settings['smart_optimization'] ) ? (int) $settings['smart_optimization'] : 1 )
: 0,
'webpQuality' => ! empty( $settings['webp_quality'] ) ? (int) $settings['webp_quality'] : 80,
'maxImageDimension' => ! empty( $settings['max_image_dimension'] ) ? (int) $settings['max_image_dimension'] : 0,
'videoOptimizationEnabled' => isset( $settings['video_optimization_enabled'] ) ? (int) $settings['video_optimization_enabled'] : 1,
Expand Down
20 changes: 20 additions & 0 deletions src/admin/css/admin-page.css
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,26 @@
order: 1;
}

.cimo-smart-optimization-toggle {
order: 2;

~ .cimo-setting-field {
order: 3;
}

~ .cimo-webp-quality-range-control {
order: 1;
}

~ .cimo-reset-button {
order: 10;
}
}

.cimo-is-premium .cimo-smart-optimization-toggle {
order: 1;
}

@keyframes cimo-bulk-optimizer-blink {

0%, 100% { opacity: 1; }
Expand Down
12 changes: 3 additions & 9 deletions src/admin/js/media-manager/drop-zone.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,18 @@ function addDropZoneListenerToMediaManager( targetDocument ) {
const progressModal = new ProgressModal( fileConverters, onCancel )
progressModal.open()

let hasError = false

// Process and optimize each media file here,
// e.g. converting to webp, resizing, compressing, etc.
const optimizedResults = await Promise.all(
fileConverters.map( async converter => {
try {
const result = await converter.convert()
const result = await converter.optimize()
if ( result.error ) {
// eslint-disable-next-line no-console
console.warn( result.error )
hasError = true
}
return result
} catch ( error ) {
hasError = true
// eslint-disable-next-line no-console
console.warn( error )
return { file: converter.file, metadata: null }
Expand Down Expand Up @@ -220,10 +216,8 @@ function addDropZoneListenerToMediaManager( targetDocument ) {
}
}

// If there's an error, do not close the progress modal so the user can read the error.
if ( ! hasError ) {
progressModal.close()
}
// Close when optimization finishes, including when we fall back to the original file after an error.
progressModal.close()
}

// Add our custom drop listener
Expand Down
111 changes: 98 additions & 13 deletions src/admin/js/media-manager/progress-modal.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,34 @@ class ProgressModal {
this.interval = null
this.modal = null
this.progressBars = []
this._setupModal()
this.delayTimeout = null
}

open() {
if ( ! this.modal ) {
return
}
if ( this.converters.length === 0 ) {
return
}
this.modal.style.display = 'flex'
this._startInterval()

const delays = this.converters
.map( c => c?.progressDelay )
.filter( v => typeof v === 'number' && v > 0 )

// Only delay if all converter opt to delay, to allow
// big media files like video to show the progress modal immediately.
const shouldDelay = delays.length === this.converters.length
const delay = shouldDelay ? Math.max( ...delays ) : 0

const start = () => {
this._setupModal()
this.modal.style.display = 'flex'
this._startInterval()
}

if ( delay > 0 ) {
this.delayTimeout = setTimeout( start, delay )
} else {
start()
}
}

_handleCloseClick() {
Expand All @@ -37,6 +53,10 @@ class ProgressModal {
}

close() {
if ( this.delayTimeout ) {
clearTimeout( this.delayTimeout )
this.delayTimeout = null
}
if ( ! this.modal ) {
return
}
Expand Down Expand Up @@ -150,9 +170,43 @@ class ProgressModal {
this.modal.appendChild( wrapper )
document.body.appendChild( this.modal )

this._ensureIndeterminateStyles()
this._renderProgressBars()
}

_ensureIndeterminateStyles() {
if ( document.getElementById( 'cimo-progress-indeterminate-styles' ) ) {
return
}
const style = document.createElement( 'style' )
style.id = 'cimo-progress-indeterminate-styles'
style.textContent = `
.cimo-progress-indeterminate-layer {
display: none;
position: absolute;
inset: 0;
overflow: hidden;
border-radius: inherit;
pointer-events: none;
}
.cimo-progress-indeterminate-bar {
position: absolute;
top: 0;
left: -35%;
width: 35%;
height: 100%;
border-radius: 5px;
background: linear-gradient(90deg, #00d8f0 0%, #2bc566 100%);
animation: cimo-indet-sweep 1.35s ease-in-out infinite;
}
@keyframes cimo-indet-sweep {
from { left: -35%; }
to { left: 100%; }
}
`
document.head.appendChild( style )
}

_renderProgressBars() {
this.progressList.innerHTML = ''
this.progressBars = []
Expand Down Expand Up @@ -259,22 +313,31 @@ class ProgressModal {
height: 8px;
width: 100%;
overflow: hidden;
position: relative;
`
const indeterminateLayer = document.createElement( 'div' )
indeterminateLayer.className = 'cimo-progress-indeterminate-layer'
const indeterminateBar = document.createElement( 'div' )
indeterminateBar.className = 'cimo-progress-indeterminate-bar'
indeterminateLayer.appendChild( indeterminateBar )

const bar = document.createElement( 'div' )
bar.className = 'cimo-progress-bar'
bar.style.cssText = `
background: linear-gradient(90deg, #00d8f0 0%, #2bc566 100%);
width: 0%;
height: 100%;
transition: width 0.5s linear;
transition: width 0.65s cubic-bezier(0.4, 0, 0.2, 1);
border-radius: 5px 0 0 5px;
`

bar.sizeLabelEl = sizeLabel
bar.statusLabelEl = statusLabel
bar.percentageLabelEl = percentageLabel
bar.indeterminateLayer = indeterminateLayer

barBg.appendChild( bar )
barBg.appendChild( indeterminateLayer )
barContainer.appendChild( label )
barContainer.appendChild( sizeLabel )
barContainer.appendChild( progressStatusFlex )
Expand All @@ -300,10 +363,19 @@ class ProgressModal {
}

_updateProgress() {
if ( ! this.modal ) {
return
}

let allDone = true
let hasError = false

this.converters.forEach( ( converter, i ) => {
const barEl = this.progressBars[ i ]
const isIndeterminate = converter.indeterminateProgress === true &&
converter.progress < 1 &&
! converter.errorMessage

let percent = converter.progress * 100

if (
Expand All @@ -325,18 +397,31 @@ class ProgressModal {
this.errorNote.style.display = 'block'
}

if ( this.progressBars[ i ] ) {
this.progressBars[ i ].statusLabelEl.style.cssText = `
if ( barEl ) {
barEl.statusLabelEl.style.cssText = `
color: ${ converter.errorMessage ? '#dc3545' : 'inherit' };
`
this.progressBars[ i ].statusLabelEl.innerText = converter.errorMessage || converter.status
this.progressBars[ i ].style.width = percent + '%'
this.progressBars[ i ].percentageLabelEl.innerText = parseInt( percent ) + '%'
barEl.statusLabelEl.innerText = converter.errorMessage || converter.status

if ( isIndeterminate ) {
barEl.style.display = 'none'
if ( barEl.indeterminateLayer ) {
barEl.indeterminateLayer.style.display = 'block'
}
barEl.percentageLabelEl.innerText = __( '…', 'cimo-image-optimizer' )
} else {
barEl.style.display = ''
if ( barEl.indeterminateLayer ) {
barEl.indeterminateLayer.style.display = 'none'
}
barEl.style.width = percent + '%'
barEl.percentageLabelEl.innerText = parseInt( percent ) + '%'
}
}
} )

// Optional: auto-close when all converters are done (progress 100%)
if ( allDone && this.modal.style.display === 'block' ) {
if ( allDone && this.modal && this.modal.style.display !== 'none' ) {
if ( ! hasError ) {
setTimeout( () => this.close(), 750 )
}
Expand Down
12 changes: 3 additions & 9 deletions src/admin/js/media-manager/select-files.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,22 +86,18 @@ function addSelectFilesListenerToFileUploads( targetDocument ) {
const progressModal = new ProgressModal( fileConverters, onCancel )
progressModal.open()

let hasError = false

// Process and optimize each media file here,
// e.g. converting to webp, resizing, compressing, etc.
const optimizedResults = await Promise.all(
fileConverters.map( async converter => {
try {
const result = await converter.convert()
const result = await converter.optimize()
if ( result.error ) {
// eslint-disable-next-line no-console
console.warn( result.error )
hasError = true
}
return result
} catch ( error ) {
hasError = true
// eslint-disable-next-line no-console
console.warn( error )
return { file: converter.file, metadata: null }
Expand Down Expand Up @@ -131,10 +127,8 @@ function addSelectFilesListenerToFileUploads( targetDocument ) {
changeEvent.__cimo_converted = true // eslint-disable-line camelcase
event.target.dispatchEvent( changeEvent )

// If there's an error, do not close the progress modal so the user can read the error.
if ( ! hasError ) {
progressModal.close()
}
// Close when optimization finishes, including when we fall back to the original file after an error.
progressModal.close()
}

if ( ! targetDocument.body.__cimo_selectfiles_listener_attached ) {
Expand Down
Loading
Loading