diff --git a/admin-bar.css b/admin-bar.css new file mode 100644 index 0000000..0f5b9dd --- /dev/null +++ b/admin-bar.css @@ -0,0 +1,42 @@ + +#wpadminbar .od-url-metrics-loading, +#wpadminbar .od-url-metrics-loading a { + cursor: wait; +} + +#wpadminbar #wp-admin-bar-od-url-metrics:has(.od-viewport-group-indicator) .ab-label { + margin-right: 5px !important; +} + +#wpadminbar .od-viewport-group-indicator { + display: inline-block; + vertical-align: middle; + width: 6px; + height: 20px; + border: solid 1px black; + background-color: gray; + opacity: 0.5; +} +#wpadminbar .od-viewport-group-indicator.od-populated { + background-color: orange; +} +#wpadminbar .od-viewport-group-indicator.od-complete { + background-color: lime; +} + +#wpadminbar #wp-admin-bar-od-url-metrics .od-viewport-group-indicator:hover { + opacity: 1; + outline: solid 2px currentColor; +} +@media screen and (max-width: 782px) { + #wpadminbar #wp-admin-bar-od-url-metrics .ab-icon { + display: none; + } + #wpadminbar #wp-admin-bar-od-url-metrics { + display: block; + } + #wpadminbar .od-viewport-group-indicator { + height: 32px; + width: 8px; + } +} diff --git a/admin-bar.js b/admin-bar.js new file mode 100644 index 0000000..2c6a484 --- /dev/null +++ b/admin-bar.js @@ -0,0 +1,53 @@ +/** + * @typedef {Object} ViewportStatus + * @property {number} min_width - Min width. + * @property {boolean} complete - Complete + * @property {number} count - Count + * @property {string} device_label - Device label. + * @property {number} sample_size - Sample size. + * @property {string|null} last_modified - Last modified label. + */ + +/** + * @param {Object} data + * @param {string} data.tooltip + * @param {string|null} data.edit_post_link + * @param {ViewportStatus[]} data.viewport_statuses + */ +export default function ( data ) { + const adminBarItem = document.getElementById( + 'wp-admin-bar-od-url-metrics' + ); + const adminBarItemLink = adminBarItem.querySelector( 'a' ); + adminBarItemLink.title = data.tooltip; + + adminBarItem.classList.remove( 'od-url-metrics-loading' ); + + for ( const viewportStatus of data.viewport_statuses ) { + const span = document.createElement( 'span' ); + span.classList.add( + 'od-viewport-group-indicator', + `od-viewport-min-width-${ viewportStatus.min_width }` + ); + if ( viewportStatus.complete ) { + span.classList.add( 'od-complete' ); + } else if ( viewportStatus.count > 0 ) { + span.classList.add( 'od-populated' ); + } else { + span.classList.add( 'od-empty' ); + } + + span.title = viewportStatus.device_label; + if ( viewportStatus.complete ) { + span.title += ', complete'; // TODO: i18n. + } + if ( viewportStatus.last_modified ) { + span.title += ', ' + viewportStatus.last_modified; + } + adminBarItemLink.appendChild( span ); + } + + if ( data.edit_post_link ) { + adminBarItemLink.href = data.edit_post_link; + } +} diff --git a/od-admin-ui.php b/od-admin-ui.php index 2b31eec..9f12f04 100644 --- a/od-admin-ui.php +++ b/od-admin-ui.php @@ -23,11 +23,11 @@ use DateTime; use DateTimeZone; use Exception; -use OD_Tag_Visitor_Registry; use OD_URL_Metric; use OD_URL_Metric_Group; use OD_URL_Metric_Group_Collection; use OD_URL_Metrics_Post_Type; +use OD_Template_Optimization_Context; use WP_Post; use WP_Post_Type; use WP_Screen; @@ -545,171 +545,145 @@ static function (): void { add_action( 'admin_bar_menu', static function ( WP_Admin_Bar $wp_admin_bar ): void { - if ( ! od_can_optimize_response() ) { - return; + if ( od_can_optimize_response() ) { + create_admin_bar_item( $wp_admin_bar ); + add_action( 'od_start_template_optimization', __NAMESPACE__ . '\populate_admin_bar_item' ); } + }, + 100 +); - $slug = od_get_url_metrics_slug( od_get_normalized_query_vars() ); - $post = OD_URL_Metrics_Post_Type::get_post( $slug ); - if ( ! ( $post instanceof WP_Post ) ) { - return; +/** + * Populates admin bar item. + * + * @param WP_Admin_Bar $wp_admin_bar Admin bar. + */ +function create_admin_bar_item( WP_Admin_Bar $wp_admin_bar ): void { + $args = array( + 'id' => 'od-url-metrics', + 'title' => '' . __( 'URL Metrics', 'od-admin-ui' ) . '', + 'href' => '#', + 'meta' => array( + 'title' => __( 'Please wait. Loading page.', 'od-admin-ui' ), + 'class' => 'od-url-metrics-loading', + ), + ); + + $wp_admin_bar->add_node( $args ); +} + +/** + * Populate admin bar item. + * + * @param OD_Template_Optimization_Context $context Template optimization context. + */ +function populate_admin_bar_item( OD_Template_Optimization_Context $context ): void { + $context->append_head_html( '' ); + + $exports = array(); + + if ( null !== $context->url_metrics_id ) { + $post = get_post( $context->url_metrics_id ); + if ( $post instanceof WP_Post ) { + $exports['edit_post_link'] = get_edit_post_link( $post, 'raw' ); } - $edit_link = get_edit_post_link( $post, 'raw' ); - if ( null === $edit_link ) { - return; + } + + $url_metrics = $context->url_metric_group_collection->get_flattened_url_metrics(); + usort( + $url_metrics, + static function ( OD_URL_Metric $a, OD_URL_Metric $b ): int { + return $b->get_timestamp() <=> $a->get_timestamp(); } + ); - $url_metrics = OD_URL_Metrics_Post_Type::get_url_metrics_from_post( $post ); - usort( - $url_metrics, - static function ( OD_URL_Metric $a, OD_URL_Metric $b ): int { - return $b->get_timestamp() <=> $a->get_timestamp(); - } - ); + $url_metrics_collection = $context->url_metric_group_collection; - // TODO: We should not have to re-construct this. - $tag_visitor_registry = new OD_Tag_Visitor_Registry(); - do_action( 'od_register_tag_visitors', $tag_visitor_registry ); - $current_etag = od_get_current_url_metrics_etag( $tag_visitor_registry, $GLOBALS['wp_query'], od_get_current_theme_template() ); - $url_metrics_collection = new OD_URL_Metric_Group_Collection( $url_metrics, $current_etag, od_get_breakpoint_max_widths(), od_get_url_metrics_breakpoint_sample_size(), od_get_url_metric_freshness_ttl() ); + $all_complete = true; - $args = array( - 'id' => 'od-url-metrics', - 'title' => '' . __( 'URL Metrics', 'od-admin-ui' ) . '', - 'href' => $edit_link, + $exports['viewport_statuses'] = array(); + foreach ( $url_metrics_collection as $group ) { + $viewport_status = array( + 'min_width' => $group->get_minimum_viewport_width(), + 'count' => $group->count(), + 'device_label' => get_device_label( $group ), + 'sample_size' => $group->get_sample_size(), + 'last_modified' => null, ); - - $all_complete = true; - - $args['title'] .= ' '; - foreach ( $url_metrics_collection as $group ) { - $etags = array_values( - array_unique( - array_map( - static function ( OD_URL_Metric $url_metric ) { - return $url_metric->get_etag(); - }, - iterator_to_array( $group ) - ) + $etags = array_values( + array_unique( + array_map( + static function ( OD_URL_Metric $url_metric ) { + return $url_metric->get_etag(); + }, + iterator_to_array( $group ) ) - ); - $is_complete = ( - $group->is_complete() - || - // Handle case using the DevMode plugin when the freshness TTL is zeroed out. - ( $group->get_freshness_ttl() === 0 && $group->count() === $group->get_sample_size() && isset( $etags[0] ) && $etags[0] === $current_etag ) - ); - $all_complete = $all_complete && $is_complete; - if ( $is_complete ) { - $class_name = 'od-complete'; - } elseif ( $group->count() > 0 ) { - $class_name = 'od-populated'; - } else { - $class_name = 'od-empty'; - } - $class_name .= sprintf( ' od-viewport-min-width-%d', $group->get_minimum_viewport_width() ); + ) + ); + $is_complete = ( + $group->is_complete() + || + // Handle case using the DevMode plugin when the freshness TTL is zeroed out. + ( $group->get_freshness_ttl() === 0 && $group->count() === $group->get_sample_size() && isset( $etags[0] ) && $etags[0] === $url_metrics_collection->get_current_etag() ) + ); + $all_complete = $all_complete && $is_complete; - $tooltip = get_device_label( $group ) . ': ' . $group->count() . '/' . $group->get_sample_size(); - if ( $is_complete ) { - $tooltip .= sprintf( ', %s', __( 'complete', 'od-admin-ui' ) ); - } + $viewport_status['complete'] = $is_complete; - $group_url_metrics = iterator_to_array( $group ); - usort( - $group_url_metrics, - static function ( OD_URL_Metric $a, OD_URL_Metric $b ): int { - return $b->get_timestamp() <=> $a->get_timestamp(); - } - ); - if ( isset( $group_url_metrics[0] ) ) { - /* translators: %s: human-readable time difference. */ - $tooltip .= ', ' . sprintf( __( '%s ago', 'default' ), human_time_diff( (int) $group_url_metrics[0]->get_timestamp() ) ); + $group_url_metrics = iterator_to_array( $group ); + usort( + $group_url_metrics, + static function ( OD_URL_Metric $a, OD_URL_Metric $b ): int { + return $b->get_timestamp() <=> $a->get_timestamp(); } + ); - $args['title'] .= sprintf( - '', - esc_attr( "od-viewport-group-indicator $class_name" ), - esc_attr( $tooltip ) - ); - } - - if ( $all_complete ) { - $args['meta']['title'] = __( 'Every viewport group is complete, being fully populated without any stale URL metrics.', 'od-admin-ui' ); - } elseif ( $url_metrics_collection->is_every_group_populated() ) { - $args['meta']['title'] = __( 'Every viewport group is populated although some URL Metrics are stale or not enough samples have been collected.', 'od-admin-ui' ); - } elseif ( $url_metrics_collection->is_any_group_populated() ) { - $args['meta']['title'] = __( 'Not every viewport group has been populated.', 'od-admin-ui' ); - } else { - $args['meta']['title'] = __( 'No URL Metrics have been collected yet.', 'od-admin-ui' ); + if ( isset( $group_url_metrics[0] ) ) { + /* translators: %s: human-readable time difference. */ + $viewport_status['last_modified'] = sprintf( __( '%s ago', 'default' ), human_time_diff( (int) $group_url_metrics[0]->get_timestamp() ) ); } - $wp_admin_bar->add_node( $args ); - add_action( 'wp_footer', __NAMESPACE__ . '\print_admin_bar_styles' ); - }, - 100 -); + $exports['viewport_statuses'][] = $viewport_status; + } -/** - * Print admin bar styles. - */ -function print_admin_bar_styles(): void { - if ( ! is_admin_bar_showing() ) { - return; + if ( $all_complete ) { + $exports['tooltip'] = __( 'Every viewport group is complete, being fully populated without any stale URL metrics.', 'od-admin-ui' ); + } elseif ( $url_metrics_collection->is_every_group_populated() ) { + $exports['tooltip'] = __( 'Every viewport group is populated although some URL Metrics are stale or not enough samples have been collected.', 'od-admin-ui' ); + } elseif ( $url_metrics_collection->is_any_group_populated() ) { + $exports['tooltip'] = __( 'Not every viewport group has been populated.', 'od-admin-ui' ); + } else { + $exports['tooltip'] = __( 'No URL Metrics have been collected yet.', 'od-admin-ui' ); } - ?> - - get_minimum_viewport_width(), $group->get_maximum_viewport_width() ), + $group->get_minimum_viewport_width() + ); + } + return $css; }