Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Site screenshot: Load mshots images via JS, show CSS loading state #172

Merged
merged 2 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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(
'<img src="%1$s" alt="%2$s" loading="%3$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 = '<a href="' . get_permalink( $post ) . '">' . $img_content . '</a>';
$classname .= ' is-linked-image';
}

$wrapper_attributes = get_block_wrapper_attributes( array( 'class' => $classname ) );
return sprintf(
'<div %s>%s</div>',
$wrapper_attributes,
$img_content
);
register_block_type( dirname( dirname( __DIR__ ) ) . '/build/site-screenshot' );
}

/**
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
/**
* Display the screenshot, using the Interactivity API to load if mshots.
*/

namespace WordPressdotorg\Theme\Showcase_2022\Site_Screenshot;

if ( ! isset( $block->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 ] ] ] );

?>
<div
<?php echo get_block_wrapper_attributes( array( 'class' => $classname ) ); // phpcs:ignore ?>
data-wp-interactive
data-wp-context="<?php echo esc_attr( $encoded_state ); ?>"
>
<?php if ( $has_link ) : ?>
<a href="<?php echo esc_url( get_permalink( $current_post ) ); ?>">
<?php endif; ?>

<?php if ( $is_mshots ) : ?>
<div
class="wporg-site-screenshot__mshot-container"
data-wp-init="effects.wporg.showcase.screenshot.init"
data-wp-effect="effects.wporg.showcase.screenshot.update"
>
<div class="wporg-site-screenshot__loader"></div>
</div>
<?php else : ?>
<img
src="<?php echo esc_url( $screenshot ); ?>"
alt="<?php echo the_title_attribute( array( 'echo' => false ) ); ?>"
loading="<?php echo $is_lazyload ? 'lazy' : 'eager'; ?>"
/>
<?php endif; ?>

<?php if ( $has_link ) : ?>
</a>
<?php endif; ?>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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 & {
Expand All @@ -45,6 +53,7 @@

a {
display: block;
height: 100%;

&:focus,
&:focus-visible {
Expand All @@ -57,13 +66,61 @@
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-color:
var(--wp--custom--wporg-site-screenshot--border--color)
var(--wp--custom--wporg-site-screenshot--border--color)
var(--wp--custom--link--color--text);
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 {
Expand Down
Original file line number Diff line number Diff line change
@@ -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' );
}
},
},
},
},
},
} );
Loading