From 6fdfe2d83d10b1584354b2dc68b6bb8ed0851df6 Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Thu, 21 Sep 2023 17:10:44 -0400 Subject: [PATCH 1/2] Site screenshot: Load mshots images via JS, show CSS loading state --- .../src/site-screenshot/block.json | 6 +- .../src/site-screenshot/index.php | 54 +------ .../src/site-screenshot/render.php | 67 ++++++++ .../src/site-screenshot/style.scss | 62 +++++++- .../src/site-screenshot/view.js | 143 ++++++++++++++++++ 5 files changed, 276 insertions(+), 56 deletions(-) create mode 100644 source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/render.php create mode 100644 source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/view.js diff --git a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/block.json b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/block.json index b7a740c9..dd8d04b8 100644 --- a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/block.json +++ b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/block.json @@ -1,6 +1,6 @@ { "$schema": "https://schemas.wp.org/trunk/block.json", - "apiVersion": 2, + "apiVersion": 3, "name": "wporg/site-screenshot", "version": "0.1.0", "title": "Site Screenshot", @@ -54,5 +54,7 @@ "usesContext": [ "postId" ], "editorScript": "file:./index.js", "editorStyle": "file:./index.css", - "style": "file:./style-index.css" + "viewScript": "file:./view.js", + "style": "file:./style-index.css", + "render": "file:../../src/site-screenshot/render.php" } diff --git a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/index.php b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/index.php index 8e926924..179b397e 100644 --- a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/index.php +++ b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/index.php @@ -22,57 +22,7 @@ * @see https://developer.wordpress.org/reference/functions/register_block_type/ */ function init() { - register_block_type( - dirname( dirname( __DIR__ ) ) . '/build/site-screenshot', - array( - 'render_callback' => __NAMESPACE__ . '\render', - ) - ); -} - -/** - * Render the block content. - * - * @param array $attributes Block attributes. - * @param string $content Block default content. - * @param WP_Block $block Block instance. - * - * @return string Returns the block markup. - */ -function render( $attributes, $content, $block ) { - if ( ! isset( $block->context['postId'] ) ) { - return ''; - } - - $post_ID = $block->context['postId']; - $post = get_post( $post_ID ); - - $screenshot = get_site_screenshot_src( $post, $attributes['type'] ); - - $loading = 'eager'; - if ( isset( $attributes['lazyLoad'] ) && true === $attributes['lazyLoad'] ) { - $loading = 'lazy'; - } - - $img_content = sprintf( - '%2$s', - esc_url( $screenshot ), - the_title_attribute( array( 'echo' => false ) ), - esc_attr( $loading ) - ); - - $classname = 'is-size-' . esc_attr( $attributes['type'] ); - if ( isset( $attributes['isLink'] ) && true == $attributes['isLink'] ) { - $img_content = '' . $img_content . ''; - $classname .= ' is-linked-image'; - } - - $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classname ) ); - return sprintf( - '
%s
', - $wrapper_attributes, - $img_content - ); + register_block_type( dirname( dirname( __DIR__ ) ) . '/build/site-screenshot' ); } /** @@ -109,7 +59,7 @@ function get_site_screenshot_src( $post, $type = 'desktop' ) { 'vpw' => 'mobile' === $type ? 375 : 1920, 'vph' => 'mobile' === $type ? 667 : 1080, ), - 'https://wordpress.com/mshots/v1/' . urlencode( $requested_url ), + 'https://s0.wp.com/mshots/v1/' . urlencode( $requested_url ), ); } elseif ( function_exists( 'jetpack_photon_url' ) ) { // Use Jetpack cache for non mShot images diff --git a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/render.php b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/render.php new file mode 100644 index 00000000..9a6fc965 --- /dev/null +++ b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/render.php @@ -0,0 +1,67 @@ +context['postId'] ) ) { + return ''; +} + +$current_post = get_post( $block->context['postId'] ); +$has_link = isset( $attributes['isLink'] ) && true == $attributes['isLink']; +$is_lazyload = isset( $attributes['lazyLoad'] ) && true === $attributes['lazyLoad']; + +$screenshot = get_site_screenshot_src( $current_post, $attributes['type'] ); +$is_mshots = str_contains( $screenshot, 'mshots' ); + +$classname = 'is-size-' . esc_attr( $attributes['type'] ); +if ( $has_link ) { + $classname .= ' is-linked-image'; +} + +// Initial state to pass to Interactivity API. +// This handles the image data (used to load image from mshots) and current +// state information (like errors). +$init_state = [ + 'isMShots' => $is_mshots, + 'isLazyLoad' => $is_lazyload, + 'attempts' => 0, + 'base64Image' => '', + 'hasError' => false, + 'src' => esc_url( $screenshot ), + 'alt' => the_title_attribute( array( 'echo' => false ) ), +]; +$encoded_state = wp_json_encode( [ 'wporg' => [ 'showcase' => [ 'screenshot' => $init_state ] ] ] ); + +?> +
$classname ) ); // phpcs:ignore ?> + data-wp-interactive + data-wp-context="" +> + + + + + +
+
+
+ + <?php echo the_title_attribute( array( 'echo' => false ) ); ?> + + + +
+ +
diff --git a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss index b27c846c..3f2b3760 100644 --- a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss +++ b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss @@ -36,6 +36,14 @@ &:first-child { flex: 4; } + + &.is-size-desktop .wporg-site-screenshot__loader { + --wporg-site-screenshot--loader--size: 100px; + } + + &.is-size-mobile .wporg-site-screenshot__loader { + --wporg-site-screenshot--loader--size: 40px; + } } .is-section-site-hero & { @@ -45,6 +53,7 @@ a { display: block; + height: 100%; &:focus, &:focus-visible { @@ -57,13 +66,62 @@ vertical-align: middle; } - &.is-size-desktop img { + &:not(.has-loaded).is-size-desktop { aspect-ratio: 535 / 300; } - &.is-size-mobile img { + &:not(.has-loaded).is-size-mobile { aspect-ratio: 375 / 600; } + + .wporg-site-screenshot__mshot-container { + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + } + + .wporg-site-screenshot__loader { + --wporg-site-screenshot--loader--size: 40px; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + + &::after { + content: ""; + display: inline-block; + box-sizing: border-box; + height: var(--wporg-site-screenshot--loader--size); + width: var(--wporg-site-screenshot--loader--size); + border: calc(var(--wporg-site-screenshot--loader--size) * 0.1) solid; + border-left-width: 0; + border-color: + var(--wp--custom--wporg-site-screenshot--border--color) + var(--wp--custom--wporg-site-screenshot--border--color) + transparent; + border-radius: 50%; + animation: rotate-360 1.4s linear infinite; + } + } + + .wporg-site-screenshot__error { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + } +} + +@keyframes rotate-360 { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } } .wp-block-group.has-feature-color-background > .wp-block-group { diff --git a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/view.js b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/view.js new file mode 100644 index 00000000..76b5d547 --- /dev/null +++ b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/view.js @@ -0,0 +1,143 @@ +/* global FileReader */ +/** + * WordPress dependencies + */ +import { store as wpStore } from '@wordpress/interactivity'; + +/** + * Module constants + */ +const MAX_ATTEMPTS = 10; +const RETRY_DELAY = 2000; + +/** + * Helper to update the "attempts" value. + * + * @param {Object} store + */ +function increaseAttempts( store ) { + store.context.wporg.showcase.screenshot.attempts++; +} + +/** + * Helper to update the "shouldRetry" value. + * + * @param {boolean} value + * @param {Object} store + */ +function setShouldRetry( value, store ) { + store.context.wporg.showcase.screenshot.shouldRetry = value; +} + +/** + * Helper to update the "hasError" value. + * + * @param {boolean} value + * @param {Object} store + */ +function setHasError( value, store ) { + store.context.wporg.showcase.screenshot.hasError = value; +} + +/** + * Helper to update the "base64Image" value. + * + * @param {string} value + * @param {Object} store + */ +function setBase64Image( value, store ) { + store.context.wporg.showcase.screenshot.base64Image = value; +} + +/** + * Make a request to the mShots URL, update state values. + * + * @param {string} fullUrl + * @param {Object} store + */ +const fetchImage = async ( fullUrl, store ) => { + try { + const res = await fetch( fullUrl ); + increaseAttempts( store ); + + if ( res.redirected ) { + setShouldRetry( true, store ); + } else if ( res.status === 200 && ! res.redirected ) { + const blob = await res.blob(); + + const reader = new FileReader(); + reader.onload = ( event ) => { + setBase64Image( event.target.result, store ); + }; + reader.readAsDataURL( blob ); + + setShouldRetry( false, store ); + } + } catch ( error ) { + setHasError( true, store ); + setShouldRetry( false, store ); + } +}; + +wpStore( { + effects: { + wporg: { + showcase: { + screenshot: { + // Run on init, starts the image fetch process. + init: async ( store ) => { + const { context } = store; + const { base64Image, isMShots, src } = context.wporg.showcase.screenshot; + + if ( isMShots && ! base64Image ) { + // Initial fetch. + await fetchImage( src, store ); + + // Set up the function to retry every RETRY_DELAY (2 seconds). + const intervalId = setInterval( + async ( _context ) => { + const { attempts, base64Image: _base64Image, shouldRetry } = _context; + if ( shouldRetry ) { + await fetchImage( src, store ); + } + if ( attempts >= MAX_ATTEMPTS ) { + clearInterval( intervalId ); + if ( ! _base64Image ) { + setHasError( true, store ); + } + } + }, + RETRY_DELAY, + context.wporg.showcase.screenshot + ); + } + }, + + // Run as an effect, when the context changes. + update: ( store ) => { + const { context, ref } = store; + const { alt, base64Image, hasError, isMShots } = context.wporg.showcase.screenshot; + + if ( ! isMShots ) { + return; + } + + if ( hasError ) { + const spinner = ref.querySelector( 'div' ); + spinner.classList.remove( 'wporg-site-screenshot__loader' ); + spinner.classList.add( 'wporg-site-screenshot__error' ); + spinner.innerText = alt; + ref.parentElement.classList.remove( 'has-loaded' ); + } else if ( base64Image ) { + const img = document.createElement( 'img' ); + img.src = base64Image; + img.alt = alt; + ref.replaceChildren( img ); + ref.parentElement.classList.add( 'has-loaded' ); + } + }, + }, + }, + }, + }, +} ); From 429433823e45181309ff56ce5ecf1d6925dd5eac Mon Sep 17 00:00:00 2001 From: Kelly Dwan Date: Fri, 22 Sep 2023 11:13:23 -0400 Subject: [PATCH 2/2] Update spinner style --- .../themes/wporg-showcase-2022/src/site-screenshot/style.scss | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss index 3f2b3760..73003c9a 100644 --- a/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss +++ b/source/wp-content/themes/wporg-showcase-2022/src/site-screenshot/style.scss @@ -96,11 +96,10 @@ height: var(--wporg-site-screenshot--loader--size); width: var(--wporg-site-screenshot--loader--size); border: calc(var(--wporg-site-screenshot--loader--size) * 0.1) solid; - border-left-width: 0; border-color: var(--wp--custom--wporg-site-screenshot--border--color) var(--wp--custom--wporg-site-screenshot--border--color) - transparent; + var(--wp--custom--link--color--text); border-radius: 50%; animation: rotate-360 1.4s linear infinite; }