diff --git a/src/class-tiny-bulk-optimization.php b/src/class-tiny-bulk-optimization.php
index 611fbf44..10c12811 100644
--- a/src/class-tiny-bulk-optimization.php
+++ b/src/class-tiny-bulk-optimization.php
@@ -24,14 +24,12 @@ class Tiny_Bulk_Optimization {
const PAGING_SIZE = 25000;
public static function get_optimization_statistics( $settings, $result = null ) {
- $stats = array();
- $stats['uploaded-images'] = 0;
- $stats['optimized-image-sizes'] = 0;
- $stats['available-unoptimized-sizes'] = 0;
- $stats['optimized-library-size'] = 0;
- $stats['unoptimized-library-size'] = 0;
- $stats['estimated_credit_use'] = 0;
- $stats['available-for-optimization'] = array();
+ $stats = array();
+ $stats['uploaded-images'] = 0;
+ $stats['optimized-library-size'] = 0;
+ $stats['unoptimized-library-size'] = 0;
+ $stats['estimated_credit_use'] = 0;
+ $stats['available-for-optimization'] = array();
if ( is_null( $result ) ) {
$last_image_id = null;
@@ -104,6 +102,8 @@ private static function populate_optimization_statistics( $settings, $result, $s
$active_sizes = $settings->get_sizes();
$active_tinify_sizes = $settings->get_active_tinify_sizes();
$conversion_enabled = $settings->get_conversion_enabled();
+ $uploads = wp_upload_dir();
+ $uploads_url = $uploads['baseurl'];
$result_count = count( $result );
for ( $i = 0; $i < $result_count; $i++ ) {
@@ -125,11 +125,9 @@ private static function populate_optimization_statistics( $settings, $result, $s
$image_stats = $tiny_image->get_statistics( $active_sizes, $active_tinify_sizes );
++$stats['uploaded-images'];
- $stats['estimated_credit_use'] += $image_stats['available_uncompressed_sizes'];
- $stats['available-unoptimized-sizes'] += $image_stats['available_unoptimized_sizes'];
- $stats['optimized-image-sizes'] += $image_stats['image_sizes_optimized'];
- $stats['optimized-library-size'] += $image_stats['compressed_total_size'];
- $stats['unoptimized-library-size'] += $image_stats['initial_total_size'];
+ $stats['estimated_credit_use'] += $image_stats['available_uncompressed_sizes'];
+ $stats['optimized-library-size'] += $image_stats['compressed_total_size'];
+ $stats['unoptimized-library-size'] += $image_stats['initial_total_size'];
if ( $conversion_enabled ) {
$stats['estimated_credit_use'] += $image_stats['available_unconverted_sizes'];
@@ -144,10 +142,44 @@ private static function populate_optimization_statistics( $settings, $result, $s
$stats['available-for-optimization'][] = array(
'ID' => $result[ $i ]['ID'],
'post_title' => $result[ $i ]['post_title'],
+ 'file' => self::get_file_name( $wp_metadata ),
+ 'sizes' => $image_stats['available_unoptimized_sizes'],
+ 'bytes' => $image_stats['compressed_total_size'],
+ 'thumbnail' => self::get_thumbnail_url( $wp_metadata, $uploads_url ),
);
}
}// End for().
return $stats;
}
+
+ /**
+ * The name of the file that was uploaded, also when WordPress scaled it down.
+ */
+ private static function get_file_name( $wp_metadata ) {
+ if ( isset( $wp_metadata['original_image'] ) ) {
+ return wp_basename( $wp_metadata['original_image'] );
+ }
+ if ( isset( $wp_metadata['file'] ) ) {
+ return wp_basename( $wp_metadata['file'] );
+ }
+ return '';
+ }
+
+ /**
+ * The URL of the image's thumbnail, built from its metadata so the page
+ * does not need a query for each image. Falls back to the image itself.
+ */
+ private static function get_thumbnail_url( $wp_metadata, $uploads_url ) {
+ if ( ! isset( $wp_metadata['file'] ) ) {
+ return '';
+ }
+ $file = $wp_metadata['file'];
+ if ( isset( $wp_metadata['sizes']['thumbnail']['file'] ) ) {
+ $directory = dirname( $file );
+ $file = ( '.' === $directory ? '' : $directory . '/' ) .
+ $wp_metadata['sizes']['thumbnail']['file'];
+ }
+ return $uploads_url . '/' . $file;
+ }
}
diff --git a/src/class-tiny-compress.php b/src/class-tiny-compress.php
index 346eaed6..c60aef76 100644
--- a/src/class-tiny-compress.php
+++ b/src/class-tiny-compress.php
@@ -40,15 +40,6 @@ public static function create( $api_key, $after_compress_callback = null ) {
return new $class( $api_key, $after_compress_callback );
}
- /* Based on pricing April 2016. */
- public static function estimate_cost( $compressions, $compressions_used ) {
- return round(
- self::compression_cost( $compressions + $compressions_used ) -
- self::compression_cost( $compressions_used ),
- 2
- );
- }
-
protected function __construct( $after_compress_callback ) {
$this->after_compress_callback = $after_compress_callback;
}
@@ -202,22 +193,4 @@ private static function needs_resize( $file, $resize_options ) {
return $should_resize_width || $should_resize_height;
}
-
- private static function compression_cost( $total ) {
- $cost = 0;
-
- if ( $total > 10000 ) {
- $compressions = $total - 10000;
- $cost += $compressions * 0.002;
- $total -= $compressions;
- }
-
- if ( $total > Tiny_Config::MONTHLY_FREE_COMPRESSIONS ) {
- $compressions = $total - Tiny_Config::MONTHLY_FREE_COMPRESSIONS;
- $cost += $compressions * 0.009;
- $total -= $compressions;
- }
-
- return $cost;
- }
}
diff --git a/src/class-tiny-plugin.php b/src/class-tiny-plugin.php
index 59539323..0bbdadcb 100644
--- a/src/class-tiny-plugin.php
+++ b/src/class-tiny-plugin.php
@@ -32,6 +32,9 @@ class Tiny_Plugin extends Tiny_WP_Base {
*/
private $background_optimizer;
+ /** @var Tiny_Dashboard */
+ private $dashboard;
+
public static function jpeg_quality() {
return 85;
}
@@ -48,7 +51,10 @@ public function __construct() {
$this->settings = new Tiny_Settings();
new Tiny_Conversion( $this->settings );
$this->background_optimizer = new Tiny_Background_Optimize( $this->settings );
- new Tiny_Dashboard( $this->settings, $this->background_optimizer );
+ $this->dashboard = new Tiny_Dashboard(
+ $this->settings,
+ $this->background_optimizer
+ );
}
public function set_compressor( $compressor ) {
@@ -285,13 +291,6 @@ public function enqueue_scripts( $hook ) {
self::version()
);
- wp_enqueue_style(
- self::NAME . '_chart',
- plugins_url( '/css/optimization-chart.css', __FILE__ ),
- array(),
- self::version()
- );
-
wp_register_script(
self::NAME . '_admin',
plugins_url( '/js/admin.js', __FILE__ ),
@@ -308,10 +307,6 @@ public function enqueue_scripts( $hook ) {
'nonce' => wp_create_nonce( 'tiny-compress' ),
'wpVersion' => self::wp_version(),
'pluginVersion' => self::version(),
- 'L10nAllDone' => __(
- 'All images are processed',
- 'tiny-compress-images'
- ),
'L10nNoActionTaken' => __(
'No action taken',
'tiny-compress-images'
@@ -321,38 +316,52 @@ public function enqueue_scripts( $hook ) {
'Mark as Compressed',
'tiny-compress-images'
),
- 'L10nCancelled' => __( 'Cancelled', 'tiny-compress-images' ),
- 'L10nStopped' => __( 'Stopped', 'tiny-compress-images' ),
- 'L10nCompressing' => __( 'Compressing', 'tiny-compress-images' ),
- 'L10nCompressed' => __( 'compressed', 'tiny-compress-images' ),
- 'L10nConverted' => __( 'converted', 'tiny-compress-images' ),
- 'L10nFile' => __( 'File', 'tiny-compress-images' ),
- 'L10nSizesOptimized' => __(
- 'Sizes optimized',
+ 'L10nWaiting' => __( 'Waiting', 'tiny-compress-images' ),
+ 'L10nCompressing' => __( 'Compressing...', 'tiny-compress-images' ),
+ 'L10nPause' => __( 'Pause', 'tiny-compress-images' ),
+ 'L10nPausing' => __( 'Pausing...', 'tiny-compress-images' ),
+ /* translators: %s: number of image sizes */
+ 'L10nSizeCount' => __( 'Sizes: %s', 'tiny-compress-images' ),
+ /* translators: %1$s: compressed size, e.g. 1.4 MB, %2$s: percentage saved, e.g. 56% */
+ 'L10nSmaller' => __( '%1$s, %2$s smaller', 'tiny-compress-images' ),
+ /* translators: %s: number of images still to compress */
+ 'L10nToGo' => __( '%s to go', 'tiny-compress-images' ),
+ /* translators: %s: number of images still to compress */
+ 'L10nToCompress' => __( '%s to compress', 'tiny-compress-images' ),
+ 'L10nLessThanMinute' => __(
+ 'Less than a minute left.',
'tiny-compress-images'
),
- 'L10nInitialSize' => __( 'Initial size', 'tiny-compress-images' ),
- 'L10nCurrentSize' => __( 'Current size', 'tiny-compress-images' ),
- 'L10nSavings' => __( 'Savings', 'tiny-compress-images' ),
- 'L10nStatus' => __( 'Status', 'tiny-compress-images' ),
- 'L10nShowMoreDetails' => __(
- 'Show more details',
+ /* translators: %s: number of minutes, with the unit abbreviated so it needs no plural */
+ 'L10nMinutesLeft' => __( 'About %s min left.', 'tiny-compress-images' ),
+ /* translators: %s: number of hours, with the unit abbreviated so it needs no plural */
+ 'L10nHoursLeft' => __( 'About %s h left.', 'tiny-compress-images' ),
+ /* translators: %1$s: bytes saved, e.g. 14.9 MB, %2$s: number of credits used */
+ 'L10nRunStatus' => __(
+ '%1$s saved so far. Credits used: %2$s.',
'tiny-compress-images'
),
- 'L10nError' => __( 'Error', 'tiny-compress-images' ),
- 'L10nLatestError' => __( 'Latest error', 'tiny-compress-images' ),
+ /*
+ * For Intl.NumberFormat, which only takes the language and region:
+ * WordPress variants like pt_PT_ao90 make it throw.
+ */
+ 'locale' => implode(
+ '-',
+ array_slice( explode( '_', get_locale() ), 0, 2 )
+ ),
+ 'L10nUnits' => array(
+ _x( 'B', 'unit symbol' ),
+ _x( 'KB', 'unit symbol' ),
+ _x( 'MB', 'unit symbol' ),
+ _x( 'GB', 'unit symbol' ),
+ _x( 'TB', 'unit symbol' ),
+ ),
'L10nInternalError' => __( 'Internal error', 'tiny-compress-images' ),
- 'L10nOutOf' => __( 'out of', 'tiny-compress-images' ),
- 'L10nWaiting' => __( 'Waiting', 'tiny-compress-images' ),
'L10nBackgroundNotice' => __(
// phpcs:ignore Generic.Files.LineLength
'Optimization continues in the background. You can close this page and come back later.',
'tiny-compress-images'
),
- 'L10nStoppedNotice' => __(
- 'Optimization stopped early.',
- 'tiny-compress-images'
- ),
'L10nInPageNotice' => __(
// phpcs:ignore Generic.Files.LineLength
'For the plugin to do the work, you need to keep this page open. But no worries: when stopped, you can continue where you left off!',
@@ -371,13 +380,6 @@ public function enqueue_scripts( $hook ) {
self::version()
);
- wp_enqueue_style(
- self::NAME . '_chart',
- plugins_url( '/css/optimization-chart.css', __FILE__ ),
- array(),
- self::version()
- );
-
wp_register_script(
self::NAME . '_tiny_bulk_optimization',
plugins_url( '/js/bulk-optimization.js', __FILE__ ),
@@ -632,31 +634,12 @@ private function bulk_image_result( $tiny_image, array $result ) {
$this->settings->get_active_tinify_sizes()
);
- $result['image_sizes_compressed'] = $image_statistics['image_sizes_compressed'];
- $result['image_sizes_converted'] = $image_statistics['image_sizes_converted'];
- $result['image_sizes_optimized'] = $image_statistics['image_sizes_optimized'];
-
- $result['initial_total_size'] = size_format(
- $image_statistics['initial_total_size'],
- 1
- );
-
$result['optimized_total_size'] = size_format(
$image_statistics['compressed_total_size'],
1
);
- $result['savings'] = $tiny_image->get_savings( $image_statistics );
- $result['status'] = $this->settings->get_status();
- $result['thumbnail'] = wp_get_attachment_image(
- $tiny_image->get_id(),
- array( '30', '30' ),
- true,
- array(
- 'class' => 'pinkynail',
- 'alt' => '',
- )
- );
+ $result['savings'] = $tiny_image->get_savings( $image_statistics );
return $result;
}
@@ -729,10 +712,7 @@ public function ajax_bulk_queue_optimize_next() {
}
/**
- * Where the images the bulk optimization page is waiting for stand.
- *
- * Finished images come with the same details the page gets when it
- * compresses an image itself, so it can show them the same way.
+ * Show a running background optimize status if any is running
*/
public function ajax_bulk_queue_status() {
if ( ! check_ajax_referer( 'tiny-compress', '_nonce', false ) ||
@@ -741,12 +721,9 @@ public function ajax_bulk_queue_status() {
wp_die( -1, 403 );
}
- $ids = isset( $_POST['ids'] ) ?
+ $ids = isset( $_POST['ids'] ) ?
explode( ',', sanitize_text_field( wp_unslash( $_POST['ids'] ) ) )
: array();
- $library_size = isset( $_POST['current_size'] ) ?
- intval( wp_unslash( $_POST['current_size'] ) )
- : 0;
$running = $this->background_optimizer->is_running();
@@ -765,11 +742,6 @@ public function ajax_bulk_queue_status() {
$item['result']
)
);
-
- /* The page applies the results in order, each on top of the last. */
- $library_size += $item['result']['size_change'];
-
- $item['result']['human_readable_library_size'] = size_format( $library_size, 2 );
}
$items[ $id ] = $item;
@@ -777,8 +749,9 @@ public function ajax_bulk_queue_status() {
wp_send_json_success(
array(
- 'running' => $running,
- 'items' => $items,
+ 'running' => $running,
+ 'items' => $items,
+ 'compression_count' => intval( $this->settings->get_compression_count() ),
)
);
}
@@ -913,27 +886,18 @@ private function render_compress_details( $tiny_image ) {
}
}
- public function get_estimated_bulk_cost( $estimated_credit_use ) {
- return Tiny_Compress::estimate_cost(
- $estimated_credit_use,
- $this->settings->get_compression_count()
- );
- }
-
public function render_bulk_optimization_page() {
$stats = Tiny_Bulk_Optimization::get_optimization_statistics( $this->settings );
-
- $estimated_costs = $this->get_estimated_bulk_cost( $stats['estimated_credit_use'] );
- $admin_colors = self::retrieve_admin_colors();
-
- /* This makes sure that up to date information is retrieved from the API. */
$this->settings->get_compressor()->get_status();
- $active_tinify_sizes = $this->settings->get_active_tinify_sizes();
- $remaining_credits = $this->settings->get_remaining_credits();
- $is_on_free_plan = $this->settings->is_on_free_plan();
- $email_address = $this->settings->get_email_address();
- $bulk_running = $this->background_optimizer->is_running();
+ $widget = $this->dashboard->get_widget_data( $stats );
+ $state = $widget['state'];
+ $compression_count = intval( $this->settings->get_compression_count() );
+
+ /* Without image sizes to compress, nothing is left and the widget data says done. */
+ if ( 'done' === $state && 0 === count( $this->settings->get_active_tinify_sizes() ) ) {
+ $state = 'no_sizes';
+ }
/* The dashboard widget links here with a nonce to start right away. */
$start_on_load = isset( $_GET['start'], $_GET['_wpnonce'] ) && wp_verify_nonce(
@@ -944,39 +908,6 @@ public function render_bulk_optimization_page() {
include __DIR__ . '/views/bulk-optimization.php';
}
- private static function retrieve_admin_colors() {
- global $_wp_admin_css_colors;
- $admin_colour_scheme = get_user_option( 'admin_color', get_current_user_id() );
- $admin_colors = array( '#0074aa', '#1685b5', '#78ca44', '#0086ba' ); // default
- if ( isset( $_wp_admin_css_colors[ $admin_colour_scheme ] ) ) {
- if ( isset( $_wp_admin_css_colors[ $admin_colour_scheme ]->colors ) ) {
- $admin_colors = $_wp_admin_css_colors[ $admin_colour_scheme ]->colors;
- }
- }
- if ( '#e5e5e5' == $admin_colors[0] && '#999' == $admin_colors[1] ) {
- $admin_colors[0] = '#bbb';
- }
- if ( '#5589aa' == $admin_colors[0] && '#cfdfe9' == $admin_colors[1] ) {
- $admin_colors[1] = '#85aec5';
- }
- if ( '#7c7976' == $admin_colors[0] && '#c6c6c6' == $admin_colors[1] ) {
- $admin_colors[1] = '#adaba9';
- $admin_colors[2] = '#adaba9';
- }
- if ( self::wp_version() > 3.7 ) {
- if ( 'fresh' == $admin_colour_scheme ) {
- $admin_colors = array( '#0074aa', '#1685b5', '#78ca44', '#0086ba' ); // better
- }
- }
- return $admin_colors;
- }
-
- public function friendly_user_name() {
- $user = wp_get_current_user();
- $name = ucfirst( empty( $user->first_name ) ? $user->display_name : $user->first_name );
- return $name;
- }
-
/**
* Will clean up converted files (if any) when the original is deleted
*
@@ -1020,19 +951,6 @@ public function backup_original_image( $attachment_id, $wp_metadata = null ) {
return $tiny_image->create_backup();
}
- public static function request_review() {
- $review_url =
- 'https://wordpress.org/support/plugin/tiny-compress-images/reviews/#new-post';
- $review_block = esc_html__( 'Enjoying TinyPNG?', 'tiny-compress-images' );
- $review_block .= ' ';
- $review_block .= sprintf(
- '%s',
- esc_url( $review_url ),
- esc_html__( 'Write a review', 'tiny-compress-images' )
- );
- return $review_block;
- }
-
/**
* Runs on uninstall
*
diff --git a/src/class-tiny-settings.php b/src/class-tiny-settings.php
index 6ebfc15a..e31ff1ce 100644
--- a/src/class-tiny-settings.php
+++ b/src/class-tiny-settings.php
@@ -193,9 +193,7 @@ public function image_sizes_notice() {
}
public function account_status() {
- if ( current_user_can( 'manage_options' ) ) {
- $this->render_account_status();
- }
+ $this->render_account_status();
exit();
}
@@ -706,6 +704,22 @@ public function render_size_checkboxes_description(
}
}
+ /**
+ * Renders the card that explains how credits are used, for pages
+ * other than the settings page.
+ *
+ * @param bool $show_settings_link Whether the card links to the settings page.
+ */
+ public function render_credits( $show_settings_link ) {
+ $tiny_sizes_count = count(
+ array_diff( $this->get_active_tinify_sizes(), array( Tiny_Image::ORIGINAL_UNSCALED ) )
+ );
+ $tiny_resize = $this->get_resize_enabled();
+ $tiny_wr2x = $this->compress_wr2x_images();
+ $tiny_convert = $this->get_conversion_enabled();
+ include __DIR__ . '/views/settings-credits.php';
+ }
+
public function render_credits_per_image( $credits ) {
echo '
';
printf(
@@ -800,9 +814,14 @@ public function after_compress_callback( $compressor ) {
}
}
- public function render_account_status() {
+ /**
+ * @param bool $link_to_settings Whether Change account links to the settings
+ * page, for pages other than the settings page.
+ */
+ public function render_account_status( $link_to_settings = false ) {
+ $is_allowed = current_user_can( 'manage_options' );
$key = $this->get_api_key();
- if ( ! empty( $key ) ) {
+ if ( $is_allowed && ! empty( $key ) ) {
if ( $this->refresh_api_key_pending() ) {
$status = (object) array(
'ok' => true,
diff --git a/src/css/bulk-optimization.css b/src/css/bulk-optimization.css
index 2b22e15b..6602ab93 100644
--- a/src/css/bulk-optimization.css
+++ b/src/css/bulk-optimization.css
@@ -1,356 +1,267 @@
-div.tiny-bulk-optimization div.dashboard {
- min-width: 750px;
- margin-top: 10px;
- border: 1px solid #e5e5e5;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
- background-color: #fff;
-}
-
-@media only screen and (max-width: 799px) {
- div.tiny-bulk-optimization div.dashboard {
- min-width: 250px;
- }
-}
-
-div.tiny-bulk-optimization div.available {
- float: left;
- width: 50%;
-}
-
-@media only screen and (max-width: 799px) {
- div.tiny-bulk-optimization div.available {
- width: 100%;
- }
-}
-
-div.tiny-bulk-optimization div.available div.inner {
- max-width: 600px;
- padding: 4px 23px 23px;
-}
-
-div.tiny-bulk-optimization div.available table.totals {
- max-width: 500px;
- width: 100%;
- margin: 18px 0;
- border: 1px solid #e5e5e5;
- border-spacing: 1px;
- box-shadow: inset 0 0 0 25px #fff;
- background-color: #e5e5e5;
-}
-
-/*ie6-8*/ div.tiny-bulk-optimization div.available table.totals {
- border-collapse: collapse \9;
- border-spacing: 0 \9;
-}
-
-/*ie6-8*/ div.tiny-bulk-optimization div.available table.totals td {
- border: 1px solid #e5e5e5 \9;
-}
-
-div.tiny-bulk-optimization div.available table.totals td.item {
- width: 33%;
- padding: 1em 8px;
- vertical-align: top;
+.tiny-bulk-hero {
+ align-items: center;
+ gap: 0;
+ padding: 40px 24px;
text-align: center;
- background-color: #fff;
}
-@media only screen and (max-width: 400px) {
- div.tiny-bulk-optimization div.available table.totals td.item.costs {
- display: none;
- }
-}
-
-div.tiny-bulk-optimization div.available table.totals td.item h3 {
- margin-top: 0;
- text-transform: uppercase;
- color: #bbb;
- font-size: 1.1em;
- font-weight: normal;
-}
-
-div.tiny-bulk-optimization div.available table.totals td.item span {
- font-size: 1.8em;
- font-weight: bold;
- line-height: 1em;
-}
-
-div.tiny-bulk-optimization div.available table.totals td.item div.tooltip {
- position: relative;
- display: inline-block;
- vertical-align: super;
-}
-
-div.tiny-bulk-optimization div.available table.totals td.item div.tooltip span.dashicons {
- font-weight: normal;
- font-size: 1.3em;
- /* WordPress color embedded in webpage */
-}
-
-* + html div.tiny-bulk-optimization div.available table.totals td.item div.tooltip span.dashicons {
+.tiny-bulk-hero[hidden] {
display: none;
}
-div.tiny-bulk-optimization div.available table.totals td.item div.tooltip div.tip {
- position: absolute;
- z-index: 10;
- display: none;
- top: 50%;
- left: 130%;
- width: 18em;
- padding: 0 1em;
- background-color: #fff;
- box-shadow: 1px 1px 4px rgba(0,0,0,0.2);
- border: 1px solid #e5e5e5;
- text-align: left;
+.tiny-bulk-panda {
+ width: 128px;
+ height: 128px;
+ margin-bottom: 16px;
}
-div.tiny-bulk-optimization div.available div.notes {
- display: table;
+.tiny-bulk-badge {
+ padding: 2px 10px;
+ border: 1px solid currentcolor;
+ border-radius: 6px;
+ font-size: 13px;
+ font-weight: 500;
+ line-height: 20px;
}
-div.tiny-bulk-optimization div.available div.notes h4 {
- display: table-cell;
- font-size: 1.1em;
- padding-right: 1.2em;
- white-space: nowrap;
+.tiny-bulk-badge-red {
+ background: #fef2f2;
+ color: #dc2626;
}
-div.tiny-bulk-optimization div.available div.notes p {
- display: table-cell;
+.tiny-bulk-badge-amber {
+ background: #fef3c7;
+ color: #b45309;
}
-div.tiny-bulk-optimization div.savings {
- margin-left: 50%;
+.tiny-bulk-badge-green {
+ background: #e8faf0;
+ color: #00b075;
}
-@media only screen and (max-width: 799px) {
- div.tiny-bulk-optimization div.savings {
- margin-left: 0;
- }
+.tiny-bulk-optimization .tiny-bulk-title {
+ margin: 12px 0 0;
+ padding: 0;
+ color: var(--tiny-text-primary);
+ font-size: 30px;
+ font-weight: 600;
+ line-height: 38px;
}
-div.tiny-bulk-optimization div.savings div.inner {
- max-width: 600px;
- padding: 4px 23px 23px;
+.tiny-bulk-hero p {
+ max-width: 540px;
+ margin: 12px 0 0;
+ color: var(--tiny-text-secondary);
+ font-size: 16px;
+ line-height: 24px;
}
-div.tiny-bulk-optimization div.savings div.legend {
- margin-left: 50%;
-}
-
-div.tiny-bulk-optimization div.savings div.legend {
- /* margin-left defined inline */
- padding: 22px 0;
+.tiny-bulk-hero .tiny-bulk-actions {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 12px;
+ margin-top: 16px;
}
-div.tiny-bulk-optimization div.savings table {
- margin: 0 auto;
+.tiny-bulk-hero .tiny-bulk-caption {
+ font-size: 14px;
+ line-height: 20px;
}
-div.tiny-bulk-optimization div.savings table td.value {
- font-size: 1.4em;
- vertical-align: top;
- text-align: right;
- padding: 15px 8px 0 5px;
- white-space: nowrap;
+.tiny-bulk-hero .tiny-bulk-caption:empty {
+ display: none;
}
-div.tiny-bulk-optimization div.savings table td.description {
- padding: 19px 0 0;
- line-height: 14px;
- vertical-align: top;
- text-align: left;
- color: #bbb;
+.tiny-bulk-progress {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ width: 100%;
+ max-width: 360px;
+ margin-top: 16px;
+ color: var(--tiny-text-primary);
+ font-size: 14px;
+ font-weight: 600;
}
-@media only screen and (max-width: 400px) {
- div.tiny-bulk-optimization div.savings div.legend {
- margin-left: 0 !important;
- padding: 10px 0;
- }
+.tiny-bulk-progress .tiny-bulk-bar {
+ flex: 1;
}
-div.tiny-bulk-optimization div.optimize {
- margin: 4px 23px 32px;
- text-align: center;
- clear: both;
+.tiny-bulk-bar {
+ height: 6px;
+ overflow: hidden;
+ background: var(--tiny-surface-subtle);
+ border-radius: 3px;
}
-div.tiny-bulk-optimization div.optimize div.progressbar {
- position: relative;
- height: 32px;
- margin-bottom: 32px;
- line-height: 32px;
- border-radius: 32px;
- font-size: 1.2em;
- font-weight: bold;
- color: #fff;
- background-color: #ebebeb;
-}
-
-div.tiny-bulk-optimization div.optimize div.progressbar div.progress {
- position: absolute;
- left: 0;
- min-width: 32px;
- width: 0;
- height: 32px;
- z-index: 2;
- border-radius: 16px;
- transition: width 1s ease;
- background-size: 40px 80px;
- -webkit-backface-visibility: hidden;
- -webkit-perspective: 1000;
- backface-visibility: hidden;
- perspective: 1000;
+.tiny-bulk-bar span {
+ display: block;
+ height: 100%;
+ background: var(--tiny-green-500);
+ transition: width 0.3s;
}
-@keyframes progress-bar {
- to {
- background-position: 3200px 0;
- }
+.tiny-bulk-library {
+ gap: 12px;
}
-div.tiny-bulk-optimization div.optimize div.progressbar div.numbers {
- position: relative;
- z-index: 3;
- text-shadow: 1px 1px 3px rgba(0,0,0,0.2);
+.tiny-bulk-library-head,
+.tiny-bulk-legend {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 8px;
}
-div.tiny-bulk-optimization div.optimize div.optimization-buttons input.button-hero {
- display: none;
+.tiny-bulk-optimization h3 {
+ margin: 0;
+ color: var(--tiny-text-primary);
+ font-size: 16px;
+ font-weight: 600;
+ line-height: 24px;
}
-div.tiny-bulk-optimization div.optimize div.optimization-buttons input.button-hero.visible {
- display: inline;
+.tiny-bulk-library-head span,
+.tiny-bulk-legend-todo {
+ color: var(--tiny-text-secondary);
}
-div.tiny-bulk-optimization div.optimize div.optimization-buttons input.button-hero {
- min-width: 230px;
+.tiny-bulk-legend {
+ color: var(--tiny-text-primary);
+ font-size: 14px;
}
-div.tiny-bulk-optimization div.optimize div.optimization-buttons input.button-hero.red {
- /* WP < 3.8 */ background-image: none;
- border-color: #920011;
- background-color: #d0021b;
- box-shadow: 0 2px 0 #920011 !important;
- text-shadow: 0 -1px 1px #920011, 1px 0 1px #920011, 0 1px 1px #920011, -1px 0 1px #920011;
+.tiny-bulk-legend span:before {
+ content: "";
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ margin-right: 8px;
+ border-radius: 2px;
}
-div.tiny-bulk-optimization div.optimize div.optimization-buttons input.button-hero.red:focus,
-div.tiny-bulk-optimization div.optimize div.optimization-buttons input.button-hero.red:hover {
- background-color: #e00016;
+.tiny-bulk-legend-done:before {
+ background: var(--tiny-green-500);
}
-div.tiny-bulk-optimization div.optimize div.optimization-buttons input.button-hero.red:active {
- background-color: #d0021b;
- box-shadow: 0 3px 0 #920011 inset !important;
+.tiny-bulk-legend-todo:before {
+ background: var(--tiny-border-emphasis);
}
-div.tiny-bulk-optimization div.optimize div.optimization-buttons p.optimization-buttons_notice {
- max-width: 420px;
- margin: 10px auto 0 auto;
-}
-div.tiny-bulk-optimization div.optimize div#optimization-spinner {
- position: absolute;
+.tiny-bulk-legend span span:before {
display: none;
- width: 20px;
- height: 20px;
- margin-top: 15px;
- background-size: 20px 20px;
-}
-
-div.tiny-bulk-optimization table.whitebox {
- min-width: 750px;
- margin-top: 16px;
- border: 1px solid #e5e5e5;
- box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04);
- background-color: #fff;
}
-@media only screen and (max-width: 799px) {
- div.tiny-bulk-optimization table.whitebox {
- min-width: 250px;
- }
+.tiny-bulk-list {
+ gap: 8px;
}
-div.tiny-bulk-optimization table.whitebox th.column-primary {
- width: 30%;
+.tiny-bulk-items {
+ margin: 0;
}
-@media only screen and (max-width: 782px) {
- div.tiny-bulk-optimization table.whitebox th.column-primary {
- width: 100%;
- }
+.tiny-bulk-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin: 0;
+ padding: 8px 0;
+ border-top: 1px solid var(--tiny-border-default);
}
-div.tiny-bulk-optimization table.whitebox th.column-author {
- /* WP 3.1-4.2 */ width: auto;
+.tiny-bulk-item:first-child {
+ border-top: 0;
}
-div.tiny-bulk-optimization table.whitebox thead th.thumbnail {
- width: 30px;
-}
-
-div.tiny-bulk-optimization table.whitebox th.thumbnail {
- width: 30px;
+.tiny-bulk-item img {
+ flex: none;
+ width: 32px;
height: 32px;
- padding-top: 6px;
- vertical-align: top;
-}
-
-div.tiny-bulk-optimization table.whitebox td {
- padding-top: 12px;
+ object-fit: cover;
+ background: var(--tiny-surface-subtle);
+ border-radius: 4px;
}
-div.tiny-bulk-optimization table.whitebox td.name,
-div.tiny-bulk-optimization table.whitebox td.status {
- padding-bottom: 12px;
+.tiny-bulk-item-name {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+ min-width: 0;
}
-div.tiny-bulk-optimization table.whitebox td.column-primary button {
- margin-top: 6px;
+.tiny-bulk-item-name strong,
+.tiny-bulk-item-name span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-div.tiny-bulk-optimization button.toggle-row {
- /* WP < 4.3 */ display: none;
+.tiny-bulk-item-name strong {
+ color: var(--tiny-text-primary);
+ font-size: 14px;
+ font-weight: 600;
}
-div.tiny-bulk-optimization table.whitebox tr.success button.toggle-row:before {
- color: #7acb44;
- content: "\f147"; /* yes */
+.tiny-bulk-item-name span {
+ color: var(--tiny-text-secondary);
+ font-size: 12px;
}
-div.tiny-bulk-optimization table.whitebox tr.failed button.toggle-row:before {
- color: #d54e21;
- content: "\f158"; /* more compatibility*/
+.tiny-bulk-item-status {
+ flex: none;
+ max-width: 50%;
+ overflow: hidden;
+ color: var(--tiny-text-secondary);
+ font-size: 14px;
+ text-overflow: ellipsis;
+ white-space: nowrap;
}
-div.tiny-bulk-optimization table.whitebox tr.no-action button.toggle-row:before {
- color: gray;
- content: "\f158"; /* more compatibility*/
+.tiny-bulk-item[data-status="compressing"] .tiny-bulk-item-status,
+.tiny-bulk-item[data-status="compressed"] .tiny-bulk-item-status {
+ color: var(--tiny-text-primary);
}
-div.tiny-bulk-optimization table.whitebox tr.failed.is-expanded span.icon {
- display: none;
+.tiny-bulk-item[data-status="compressing"] .tiny-bulk-item-status:before {
+ content: "";
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ margin-right: 8px;
+ vertical-align: -2px;
+ border: 2px solid var(--tiny-surface-subtle);
+ border-top-color: var(--tiny-green-500);
+ border-radius: 50%;
+ animation: tiny-bulk-spin 1s linear infinite;
}
-div.tiny-bulk-optimization div.upgrade-account-notice {
- padding: 22px 28px;
- background-color: #F5F9FA;
- text-align: center;
+.tiny-bulk-item[data-status="error"] .tiny-bulk-item-status {
+ color: var(--tiny-red-500);
}
-div.tiny-bulk-optimization div.upgrade-account-notice div.introduction {
- padding-bottom: 10px;
+@keyframes tiny-bulk-spin {
+ to {
+ transform: rotate(360deg);
+ }
}
-div.tiny-bulk-optimization div.upgrade-account-notice p {
- margin: 5px 0;
+@media (prefers-reduced-motion: reduce) {
+ .tiny-bulk-item[data-status="compressing"] .tiny-bulk-item-status:before {
+ animation: none;
+ }
}
-div.tiny-bulk-optimization div.upgrade-account-notice p a {
- display: block;
- margin-top: 10px;
- color: #858A93;
+.tiny-bulk-footer {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ gap: 8px;
+ padding: 16px 24px;
+ color: var(--tiny-text-secondary);
+ font-size: 14px;
+ background: var(--tiny-surface-page);
+ border-top: 1px solid var(--tiny-border-default);
+ border-radius: 0 0 12px 12px;
}
diff --git a/src/css/optimization-chart.css b/src/css/optimization-chart.css
deleted file mode 100644
index 8d3b6f4a..00000000
--- a/src/css/optimization-chart.css
+++ /dev/null
@@ -1,52 +0,0 @@
-div.tiny-optimization-chart {
- float: left;
- position: relative;
- width: 50%;
- text-align: center;
-}
-
-.ie8 div.tiny-optimization-chart {
- margin: 0;
-}
-
-div.tiny-optimization-chart svg circle.main {
- fill: #ebebeb;
- animation: shwoosh 2s ease;
- transition: stroke-dasharray 1s ease;
-}
-
-div.tiny-optimization-chart svg circle.inner {
- fill: #fff;
-}
-
-div.tiny-optimization-chart div.value {
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- text-align: center;
-}
-
-div.tiny-optimization-chart div.value div.percentage {
- width: 50%;
- margin-left: auto;
- margin-right: auto;
- margin-top: 56px;
- font-size: 2.1em;
- line-height: 1em;
-}
-
-div.tiny-optimization-chart div.value div.label {
- width: 50%;
- margin-left: auto;
- margin-right: auto;
- font-size: 1.4em;
- line-height: 1.2em;
-}
-
-@media only screen and (max-width: 400px) {
- div.tiny-bulk-optimization div.tiny-optimization-chart {
- float: none;
- width: 100%;
- }
-}
diff --git a/src/js/bulk-optimization.js b/src/js/bulk-optimization.js
index 5da2a34e..8cec388e 100644
--- a/src/js/bulk-optimization.js
+++ b/src/js/bulk-optimization.js
@@ -1,352 +1,333 @@
(function() {
const POLL_INTERVAL = 2000;
const POLL_WINDOW = 10;
+ const POLL_ATTEMPTS = 5;
const PAGE_WORKERS = 5;
+ const config = window.tinyBulk;
+ const items = config.items;
let pending = [];
- let stoppedEarly = false;
let pageWorkers = 0;
+ let finished = 0;
+ let compressed = 0;
+ let savedBytes = 0;
+ let creditsUsed = 0;
+ let failedPolls = 0;
+
+ function findPageElement(name) {
+ return document.querySelector(`[data-tiny-bulk="${name}"]`);
+ }
- function updateProgressBar(successFullCompressions) {
- var totalToOptimize = parseInt(jQuery('div#compression-progress-bar').data('number-to-optimize'), 10);
-
- var optimizedSoFar = parseInt(jQuery('#optimized-so-far').text(), 10);
- jQuery('#optimized-so-far').html(successFullCompressions + optimizedSoFar);
-
- var percentage = '100%';
- if (totalToOptimize > 0) {
- percentage = Math.round((successFullCompressions + optimizedSoFar) / totalToOptimize * 100, 1) + '%';
+ function setText(name, text) {
+ const target = findPageElement(name);
+ if (target) {
+ target.textContent = text;
}
- jQuery('div#compression-progress-bar #progress-size').css('width', percentage);
- jQuery('div#compression-progress-bar #percentage').html('(' + percentage + ')');
-
- var numberToOptimize = parseInt(jQuery('#optimizable-image-sizes').html(), 10);
- jQuery('#optimizable-image-sizes').html(numberToOptimize - successFullCompressions);
}
- function updateSavings(successFullCompressions, successFullSaved, newHumanReadableLibrarySize) {
- window.currentLibraryBytes = window.currentLibraryBytes + successFullSaved;
+ // Fills in %s, or %1$s, %2$s, like PHP's sprintf.
+ function format(template) {
+ const values = Array.prototype.slice.call(arguments, 1);
+ let next = 0;
+ return template.replace(/%(?:(\d)\$)?s/g, function(match, position) {
+ return position ? values[position - 1] : values[next++];
+ });
+ }
- var imagesSizedOptimized = parseInt(jQuery('#optimized-image-sizes').text(), 10) + successFullCompressions;
- var initialLibraryBytes = parseInt(jQuery('#unoptimized-library-size').data('bytes'), 10);
- var percentage = (1 - window.currentLibraryBytes / initialLibraryBytes);
- var chartSize = jQuery('div#optimization-chart').data('full-circle-size');
+ // Numbers follow the WordPress locale, like PHP's number_format_i18n.
+ const numberFormat = new Intl.NumberFormat(tinyCompress.locale);
+ const sizeFormat = new Intl.NumberFormat(tinyCompress.locale, {
+ minimumFractionDigits: 1,
+ maximumFractionDigits: 1
+ });
- jQuery('#optimized-image-sizes').html(imagesSizedOptimized);
- jQuery('#optimized-library-size').attr('data-bytes', window.currentLibraryBytes);
- jQuery('#optimized-library-size').html(newHumanReadableLibrarySize);
- jQuery('#savings-percentage').html(Math.round(percentage * 1000) / 10 + '%');
- jQuery('div#optimization-chart svg circle.main').css('stroke-dasharray', '' + (chartSize * percentage) + ' ' + chartSize);
+ /**
+ * Formats bytes in the largest unit that keeps the value at 1 or more, with
+ * one decimal like PHP's size_format( $bytes, 1 ). Nothing or less than
+ * nothing shows as 0 bytes.
+ * @param number bytes
+ */
+ function formatBytes(bytes) {
+ const units = tinyCompress.L10nUnits;
+ let value = Math.max(0, bytes || 0);
+ let unit = 0;
+ while (value >= 1024 && unit < units.length - 1) {
+ value /= 1024;
+ unit++;
+ }
+ const valueFormat = unit === 0 ? numberFormat : sizeFormat;
+ return `${valueFormat.format(value)} ${units[unit]}`;
}
- function handleCancellation() {
- jQuery('div#bulk-optimization-actions').hide();
- jQuery('div.progress').css('animation', 'none');
+ async function post(action, data) {
+ const body = new URLSearchParams(Object.assign({ _nonce: tinyCompress.nonce, action: action }, data));
+ const response = await fetch(ajaxurl, { method: 'POST', body: body, credentials: 'same-origin' });
+ if (!response.ok) {
+ const error = new Error(response.statusText);
+ error.status = response.status;
+ throw error;
+ }
+ const json = await response.json();
+ return json.data;
}
- function updateRowAfterCompression(row, data) {
- var successFullCompressions = parseInt(data.success, 10);
- var successFullSaved = parseInt(data.size_change, 10);
- var newHumanReadableLibrarySize = data.human_readable_library_size;
- if (successFullCompressions === 0) {
- row.addClass('no-action');
- row.find('.status').html('' + tinyCompress.L10nNoActionTaken).attr('data-status', 'no-action-taken');
- } else {
- row.addClass('success');
- const rowResultElement = row.find('.status');
- rowResultElement.attr('data-status', 'compressed');
- const icon = '';
-
- let successHTML = '';
- if (data.image_sizes_compressed > 0) {
- successHTML += `
${icon} ${data.image_sizes_compressed} ${tinyCompress.L10nCompressed}
`;
- }
+ function showHero(state) {
+ document.querySelectorAll('[data-tiny-bulk-hero]').forEach(function(hero) {
+ hero.hidden = hero.getAttribute('data-tiny-bulk-hero') !== state;
+ });
+ }
- if (data.image_sizes_converted > 0) {
- successHTML += `${icon} ${data.image_sizes_converted} ${tinyCompress.L10nConverted}
`;
+ function drawRows() {
+ const list = document.getElementById('tiny-bulk-items');
+ if (!list) {
+ return;
+ }
+ items.forEach(function(item, i) {
+ const row = document.createElement('li');
+ row.className = 'tiny-bulk-item';
+ row.setAttribute('data-status', 'waiting');
+
+ const thumbnail = document.createElement('img');
+ thumbnail.loading = 'lazy';
+ thumbnail.alt = '';
+ if (item.thumbnail) {
+ thumbnail.src = item.thumbnail;
}
- rowResultElement.html(successHTML);
-
- updateProgressBar(successFullCompressions);
- updateSavings(successFullCompressions, successFullSaved, newHumanReadableLibrarySize);
- }
+ const name = document.createElement('div');
+ name.className = 'tiny-bulk-item-name';
+ const title = document.createElement('strong');
+ title.textContent = item.post_title;
+ const details = document.createElement('span');
+ const sizeCount = format(tinyCompress.L10nSizeCount, numberFormat.format(item.sizes));
+ details.textContent = `${item.file}, ${sizeCount}, ${formatBytes(item.bytes)}`;
+ name.append(title, details);
+
+ const status = document.createElement('span');
+ status.className = 'tiny-bulk-item-status';
+ status.setAttribute('data-testid', `bulk-item-status-${i}`);
+ status.textContent = tinyCompress.L10nWaiting;
+
+ row.append(thumbnail, name, status);
+ list.append(row);
+ });
}
- function bulkOptimizationCallback(data, items, i) {
- var row = jQuery('#optimization-items tr').eq(parseInt(i, 10)+1);
+ function setRowStatus(i, status, text) {
+ const row = document.getElementById('tiny-bulk-items').children[i];
+ row.setAttribute('data-status', status);
+ row.querySelector('.tiny-bulk-item-status').textContent = text;
+ return row;
+ }
- if (data.failed > 0) {
- row.addClass('failed');
- row.find('.status').html('' + tinyCompress.L10nLatestError + ': ' + data.message + '');
- row.find('.status').attr('title', data.message);
- row.find('.status').attr('data-status', 'error');
+ function applyResult(i, result) {
+ finished++;
+ if (result.failed > 0) {
+ setRowStatus(i, 'error', result.message).title = result.message;
+ } else if (parseInt(result.success, 10) === 0) {
+ setRowStatus(i, 'no-action', tinyCompress.L10nNoActionTaken);
} else {
- updateRowAfterCompression(row, data);
+ compressed++;
+ savedBytes -= parseInt(result.size_change, 10);
+ setRowStatus(i, 'compressed', format(
+ tinyCompress.L10nSmaller,
+ result.optimized_total_size,
+ `${Math.round(result.savings)}%`
+ ));
}
+ }
- row.find('.name').html(items[i].post_title + '');
-
- if (!data.image_sizes_compressed) {
- data.image_sizes_compressed = '-';
- }
- if (!data.initial_total_size) {
- data.initial_total_size = '-';
- }
- if (!data.optimized_total_size) {
- data.optimized_total_size = '-';
+ function secondsLeftText() {
+ const sizesLeft = pending.reduce(function(sum, i) {
+ return sum + items[i].sizes;
+ }, 0);
+ const seconds = Math.round(sizesLeft / config.workers);
+ if (seconds < 60) {
+ return tinyCompress.L10nLessThanMinute;
}
- if (!data.savings || data.savings === 0) {
- data.savings = '-';
- } else {
- data.savings += '%';
+ if (seconds < 90 * 60) {
+ return format(tinyCompress.L10nMinutesLeft, numberFormat.format(Math.round(seconds / 60)));
}
+ return format(tinyCompress.L10nHoursLeft, numberFormat.format(Math.round(seconds / 3600)));
+ }
- row.find('.thumbnail').html(data.thumbnail);
- row.find('.sizes-compressed').html(data.image_sizes_compressed);
- row.find('.initial-size').html(data.initial_total_size);
- row.find('.optimized-size').html(data.optimized_total_size);
- row.find('.savings').html(data.savings);
+ function updateNumbers() {
+ const hasItems = items.length > 0;
+ const itemsRunningPerc = hasItems ? Math.floor(finished / items.length * 100) : 0;
+ const runPercentage = `${itemsRunningPerc}%`;
+ setText('run-done', numberFormat.format(finished));
+ setText('run-percentage', runPercentage);
+ findPageElement('run-bar').style.width = runPercentage;
+ const runStatus = format(
+ tinyCompress.L10nRunStatus,
+ formatBytes(savedBytes),
+ numberFormat.format(creditsUsed)
+ );
+ setText('run-status', `${secondsLeftText()} ${runStatus}`);
+
+ const libraryCompressed = config.imagesOptimized + compressed;
+ const libraryPercentage = Math.floor(libraryCompressed / config.imagesTotal * 100);
+ setText('library-percentage', numberFormat.format(libraryPercentage));
+ setText('library-compressed', numberFormat.format(libraryCompressed));
+ setText('library-todo', format(tinyCompress.L10nToGo, numberFormat.format(config.imagesRemaining - compressed)));
+ findPageElement('library-bar').style.width = `${libraryPercentage}%`;
+ setText('waiting', numberFormat.format(pending.length));
+
+ const librarySaved = config.bytesSaved + savedBytes;
+ setText('stat-compressed', numberFormat.format(libraryCompressed));
+ setText('stat-saved', formatBytes(librarySaved));
+ setText('stat-average', `${Math.round(librarySaved / config.initialSize * 100)}%`);
+ setText('done-saved', formatBytes(librarySaved));
}
- function finishOptimization(message) {
- if (message) {
- const notice = jQuery('');
- notice.find('p').text(message);
- notice.insertAfter(jQuery('#tiny-bulk-optimization h2'));
+ // Shows the done hero when every image was compressed. Otherwise the run was
+ // paused or stopped, some images failed, or the page had no images listed,
+ // and the page reloads to show what is left.
+ function finishRun() {
+ if (items.length === 0 || pending.length > 0 || compressed < items.length) {
+ location.reload();
+ return;
}
- jQuery('div#optimization-spinner').css('display', 'none');
- handleCancellation();
+ const title = findPageElement('list-title');
+ title.textContent = title.getAttribute('data-done');
+ findPageElement('list-waiting').hidden = true;
+ setText('library-todo', format(tinyCompress.L10nToCompress, '0'));
+ showHero('done');
}
// The queue works through the images in the order they are listed, so only
// the first few waiting ones need asking about.
- function pollStatus(items) {
+ async function pollStatus() {
const batch = pending.slice(0, POLL_WINDOW);
- drawSomeRows(items, batch[batch.length - 1] + 1);
- jQuery.post(ajaxurl, {
- _nonce: tinyCompress.nonce,
- action: 'tiny_bulk_queue_status',
- ids: batch.map(function(i) { return items[i].ID; }).join(','),
- current_size: window.currentLibraryBytes
- }, function(response) {
- const data = response.data;
+ try {
+ const data = await post('tiny_bulk_queue_status', {
+ ids: batch.map(function(i) { return items[i].ID; }).join(',')
+ });
const running = data.running || pageWorkers > 0;
- let finished = 0;
- const waiting = batch.filter(function(i) {
+ const finishedBefore = finished;
+ creditsUsed = data.compression_count - config.compressionCount;
+
+ batch.forEach(function(i) {
const item = data.items[items[i].ID] || {};
if (item.status === 'done' || item.status === 'failed') {
- bulkOptimizationCallback(item.result, items, i);
- finished++;
- } else if (running && (item.status === 'queued' || item.status === 'processing')) {
- return true;
- } else {
- // Unless this page cancelled, the run ended because every worker stopped.
- const label = window.optimizationCancelled ? tinyCompress.L10nCancelled : tinyCompress.L10nStopped;
- jQuery('#optimization-items tr').eq(i + 1).find('.status').html(label).attr('data-status', 'cancelled');
- stoppedEarly = true;
+ applyResult(i, item.result);
+ pending = pending.filter(function(p) { return p !== i; });
+ } else if (item.status === 'processing') {
+ setRowStatus(i, 'compressing', tinyCompress.L10nCompressing);
}
- return false;
});
- pending = waiting.concat(pending.slice(batch.length));
+ updateNumbers();
+ failedPolls = 0;
- if (pending.length > 0 && (running || finished > 0)) {
- setTimeout(pollStatus, POLL_INTERVAL, items);
- } else if (window.optimizationCancelled) {
- finishOptimization(null);
+ // A run can still be going when this page has no images waiting for it,
+ // e.g. when the page loaded just as the run was finishing up.
+ if (running || (pending.length > 0 && finished > finishedBefore)) {
+ setTimeout(pollStatus, POLL_INTERVAL);
} else {
- finishOptimization(stoppedEarly ? tinyCompress.L10nStoppedNotice : tinyCompress.L10nAllDone);
+ finishRun();
}
- }, 'json').fail(function(xhr) {
- if (xhr.status === 403) {
- finishOptimization(tinyCompress.L10nInternalError);
+ } catch (error) {
+ failedPolls++;
+ if (failedPolls < POLL_ATTEMPTS) {
+ setTimeout(pollStatus, POLL_INTERVAL);
} else {
- setTimeout(pollStatus, POLL_INTERVAL, items);
+ setText('run-notice', tinyCompress.L10nInternalError);
}
- });
+ }
}
/**
* When the page cannot reach itself (loopbacks are not supported),
* the page works through the queue one image per request until none are left
*/
- function optimizeInPage() {
- jQuery.post(ajaxurl, {
- _nonce: tinyCompress.nonce,
- action: 'tiny_bulk_queue_optimize_next'
- }, function(response) {
- if (response.data.optimized) {
+ async function optimizeInPage() {
+ try {
+ const data = await post('tiny_bulk_queue_optimize_next');
+ if (data.optimized) {
optimizeInPage();
} else {
pageWorkers--;
}
- }, 'json').fail(function(xhr) {
- if (xhr.status === 403) {
+ } catch (error) {
+ if (error.status === 403) {
pageWorkers--;
} else {
setTimeout(optimizeInPage, POLL_INTERVAL);
}
- });
- }
-
- function prepareBulkOptimization(items, running, startOnLoad) {
- window.allBulkOptimizationItems = items;
- updateProgressBar(0);
- if (running) {
- startBulkOptimization(items, true);
}
- startFromUrl(items, running, startOnLoad);
}
- // The dashboard widget opens this page with &start=1 and a nonce, which the
- // server checks. Start right away, then drop both from the URL so a reload
- // does not start again.
- function startFromUrl(items, running, startOnLoad) {
- const url = new URL(location.href);
- if (url.searchParams.has('start')) {
- url.searchParams.delete('start');
- url.searchParams.delete('_wpnonce');
- history.replaceState(null, '', url);
- }
- if (!startOnLoad || running) {
- return;
- }
-
- const start = document.getElementById('id-start');
- if (start && start.offsetParent !== null) {
- jQuery('#hide-warning').trigger('click');
- startBulkOptimization(items);
+ function showRun() {
+ const title = findPageElement('list-title');
+ if (title) {
+ title.textContent = title.getAttribute('data-compressing');
}
+ showHero('compressing');
+ updateNumbers();
}
- function startBulkOptimization(items, running) {
- window.optimizationCancelled = false;
- window.totalRowsDrawn = 0;
- window.currentLibraryBytes = parseInt(jQuery('#optimized-library-size').data('bytes'), 10);
+ async function startRun() {
pending = items.map(function(item, i) { return i; });
- stoppedEarly = false;
-
- const notice = jQuery('div#bulk-optimization-actions p.optimization-buttons_notice');
- jQuery('div#bulk-optimization-actions input').removeClass('visible');
- jQuery('div.progress').css('animation', 'progress-bar 80s linear infinite');
- jQuery('div#optimization-spinner').css('display', 'inline-block');
- updateProgressBar(0);
-
- if (running) {
- notice.text(tinyCompress.L10nBackgroundNotice);
- jQuery('div#bulk-optimization-actions input#id-optimizing').addClass('visible');
- pollStatus(items);
- return;
- }
-
- jQuery.post(ajaxurl, {
- _nonce: tinyCompress.nonce,
- action: 'tiny_bulk_queue_start'
- }, function(response) {
- if (response.data.background) {
- notice.text(tinyCompress.L10nBackgroundNotice);
+ showRun();
+ try {
+ const data = await post('tiny_bulk_queue_start');
+ if (data.background) {
+ setText('run-notice', tinyCompress.L10nBackgroundNotice);
} else {
- notice.text(tinyCompress.L10nInPageNotice);
+ setText('run-notice', tinyCompress.L10nInPageNotice);
for (let i = 0; i < PAGE_WORKERS; i++) {
pageWorkers++;
optimizeInPage();
}
}
- jQuery('div#bulk-optimization-actions input#id-optimizing').addClass('visible');
- pollStatus(items);
- }, 'json').fail(function() {
- finishOptimization(tinyCompress.L10nInternalError);
- });
+ pollStatus();
+ } catch (error) {
+ setText('run-notice', tinyCompress.L10nInternalError);
+ }
}
- function drawSomeRows(items, end) {
- const list = jQuery('#optimization-items tbody');
- const start = window.totalRowsDrawn;
- end = Math.min(end, items.length);
- for (let i = start; i < end; i++) {
- const tableRow = `
- |
- ${items[i].post_title}
- | |
- |
- |
- ${tinyCompress.L10nWaiting} |
-
`;
- list.append(tableRow);
+ async function pauseRun() {
+ const button = findPageElement('pause');
+ if ( !button ) {
+ return;
}
- window.totalRowsDrawn = Math.max(start, end);
- }
- async function cancelOptimization() {
+ button.disabled = true;
+ button.textContent = tinyCompress.L10nPausing;
try {
- window.optimizationCancelled = true;
- jQuery('div#optimization-spinner').css('display', 'none');
- jQuery('div#bulk-optimization-actions input').removeClass('visible');
- jQuery('div#bulk-optimization-actions input#id-cancelling').addClass('visible');
- await jQuery.post(ajaxurl, {
- _nonce: tinyCompress.nonce,
- action: 'tiny_bulk_queue_cancel',
- });
- } catch (err) {
- // Cancel failed, revert state
- window.optimizationCancelled = false;
- jQuery('div#optimization-spinner').css('display', 'inline-block');
- jQuery('div#bulk-optimization-actions input').removeClass('visible');
- jQuery('div#bulk-optimization-actions input#id-optimizing').addClass('visible');
- jQuery('div#bulk-optimization-actions p.optimization-buttons_notice').text(tinyCompress.L10nInternalError);
+ await post('tiny_bulk_queue_cancel');
+ } catch (error) {
+ button.disabled = false;
+ button.textContent = tinyCompress.L10nPause;
+ setText('run-notice', tinyCompress.L10nInternalError);
}
}
- jQuery('.tiny-bulk-optimization .upgrade-account-notice a#hide-warning').click(function() {
- jQuery('.tiny-bulk-optimization .upgrade-account-notice').hide();
- jQuery('.tiny-bulk-optimization .optimize').children().show();
- });
+ // The dashboard widget opens this page with &start=1 and a nonce.
+ // Both are removed from the URL so a reload does not start again.
+ const url = new URL(location.href);
+ if (url.searchParams.has('start')) {
+ url.searchParams.delete('start');
+ url.searchParams.delete('_wpnonce');
+ history.replaceState(null, '', url);
+ }
- jQuery('div#bulk-optimization-actions input').click(function() {
- if ((jQuery(this).attr('id') === 'id-start') && jQuery(this).hasClass('visible')) {
- startBulkOptimization(window.allBulkOptimizationItems);
- }
- if ((jQuery(this).attr('id') === 'id-cancel') && jQuery(this).hasClass('visible')) {
- cancelOptimization();
- }
- });
+ drawRows();
- jQuery('div#bulk-optimization-actions input').hover(function() {
- if ((jQuery(this).attr('id') === 'id-optimizing') && jQuery(this).hasClass('visible')) {
- window.lastActiveButton = jQuery('div#bulk-optimization-actions input.visible');
- window.lastActiveButton.removeClass('visible');
- jQuery('div#bulk-optimization-actions input#id-cancel').addClass('visible');
- }
- }, function() {
- if ((jQuery(this).attr('id') === 'id-cancel') && jQuery(this).hasClass('visible')) {
- window.lastActiveButton.addClass('visible');
- jQuery('div#bulk-optimization-actions input#id-cancel').removeClass('visible');
- }
+ document.querySelectorAll('[data-tiny-bulk="start"]').forEach(function(button) {
+ button.addEventListener('click', startRun);
});
- function attachToolTipEventHandlers() {
- var tooltip = '#tiny-bulk-optimization div.tooltip';
- var tip = 'div.tip';
- var toolTipTimeout = null;
- jQuery(tooltip).mouseleave(function(){
- var that = this;
- toolTipTimeout = setTimeout(function() {
- if (jQuery(that).find(tip).is(':visible')) {
- jQuery(tooltip).find(tip).hide();
- }
- }, 100);
- });
- jQuery(tooltip).mouseenter(function(){
- jQuery(this).find(tip).show();
- clearTimeout(toolTipTimeout);
- });
- jQuery(tooltip).find(tip).mouseenter(function(){
- clearTimeout(toolTipTimeout);
- });
+ const pauseElement = findPageElement('pause');
+ if (pauseElement) {
+ pauseElement.addEventListener('click', pauseRun);
}
- attachToolTipEventHandlers();
-
- window.bulkOptimizationAutorun = startBulkOptimization;
- window.bulkOptimization = prepareBulkOptimization;
-
+ if (config.running) {
+ pending = items.map(function(item, i) { return i; });
+ setText('run-notice', tinyCompress.L10nBackgroundNotice);
+ showRun();
+ pollStatus();
+ } else if (config.startOnLoad && findPageElement('start')) {
+ startRun();
+ }
}).call();
diff --git a/src/views/account-status-connected.php b/src/views/account-status-connected.php
index d1f01bfe..19d20239 100644
--- a/src/views/account-status-connected.php
+++ b/src/views/account-status-connected.php
@@ -9,6 +9,7 @@
* @type string|null $message Optional status message.
* }
* @var string $key The API key.
+ * @var bool $link_to_settings Whether Change account links to the settings page.
*/
// Every account gets free credits each month. Only free accounts have a
@@ -150,6 +151,10 @@
);
?>
+
+
+
+