summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/jetpack/extensions/blocks/slideshow/slideshow.js')
-rw-r--r--plugins/jetpack/extensions/blocks/slideshow/slideshow.js232
1 files changed, 232 insertions, 0 deletions
diff --git a/plugins/jetpack/extensions/blocks/slideshow/slideshow.js b/plugins/jetpack/extensions/blocks/slideshow/slideshow.js
new file mode 100644
index 00000000..b7d97c1a
--- /dev/null
+++ b/plugins/jetpack/extensions/blocks/slideshow/slideshow.js
@@ -0,0 +1,232 @@
+/**
+ * External dependencies
+ */
+import classnames from 'classnames';
+import ResizeObserver from 'resize-observer-polyfill';
+import { __ } from '@wordpress/i18n';
+import { Component, createRef } from '@wordpress/element';
+import { isBlobURL } from '@wordpress/blob';
+import { isEqual } from 'lodash';
+import { RichText } from '@wordpress/editor';
+import { Spinner } from '@wordpress/components';
+
+/**
+ * Internal dependencies
+ */
+import createSwiper from './create-swiper';
+import {
+ swiperApplyAria,
+ swiperInit,
+ swiperPaginationRender,
+ swiperResize,
+} from './swiper-callbacks';
+
+class Slideshow extends Component {
+ pendingRequestAnimationFrame = null;
+ resizeObserver = null;
+ static defaultProps = {
+ effect: 'slide',
+ };
+
+ constructor( props ) {
+ super( props );
+
+ this.slideshowRef = createRef();
+ this.btnNextRef = createRef();
+ this.btnPrevRef = createRef();
+ this.paginationRef = createRef();
+ }
+
+ componentDidMount() {
+ const { onError } = this.props;
+ this.buildSwiper()
+ .then( swiper => {
+ this.swiperInstance = swiper;
+ this.initializeResizeObserver( swiper );
+ } )
+ .catch( () => {
+ onError( __( 'The Swiper library could not be loaded.', 'jetpack' ) );
+ } );
+ }
+
+ componentWillUnmount() {
+ this.clearResizeObserver();
+ this.clearPendingRequestAnimationFrame();
+ }
+
+ componentDidUpdate( prevProps ) {
+ const { align, autoplay, delay, effect, images, onError } = this.props;
+
+ /* A change in alignment or images only needs an update */
+ if ( align !== prevProps.align || ! isEqual( images, prevProps.images ) ) {
+ this.swiperInstance && this.swiperInstance.update();
+ }
+ /* A change in effect requires a full rebuild */
+ if (
+ effect !== prevProps.effect ||
+ autoplay !== prevProps.autoplay ||
+ delay !== prevProps.delay ||
+ images !== prevProps.images
+ ) {
+ let realIndex;
+ if ( ! this.swiperIndex ) {
+ realIndex = 0;
+ } else if ( images.length === prevProps.images.length ) {
+ realIndex = this.swiperInstance.realIndex;
+ } else {
+ realIndex = prevProps.images.length;
+ }
+ this.swiperInstance && this.swiperInstance.destroy( true, true );
+ this.buildSwiper( realIndex )
+ .then( swiper => {
+ this.swiperInstance = swiper;
+ this.initializeResizeObserver( swiper );
+ } )
+ .catch( () => {
+ onError( __( 'The Swiper library could not be loaded.', 'jetpack' ) );
+ } );
+ }
+ }
+
+ initializeResizeObserver = swiper => {
+ this.clearResizeObserver();
+ this.resizeObserver = new ResizeObserver( () => {
+ this.clearPendingRequestAnimationFrame();
+ this.pendingRequestAnimationFrame = requestAnimationFrame( () => {
+ swiperResize( swiper );
+ swiper.update();
+ } );
+ } );
+ this.resizeObserver.observe( swiper.el );
+ };
+
+ clearPendingRequestAnimationFrame = () => {
+ if ( this.pendingRequestAnimationFrame ) {
+ cancelAnimationFrame( this.pendingRequestAnimationFrame );
+ this.pendingRequestAnimationFrame = null;
+ }
+ };
+
+ clearResizeObserver = () => {
+ if ( this.resizeObserver ) {
+ this.resizeObserver.disconnect();
+ this.resizeObserver = null;
+ }
+ };
+
+ render() {
+ const { autoplay, className, delay, effect, images } = this.props;
+ // Note: React omits the data attribute if the value is null, but NOT if it is false.
+ // This is the reason for the unusual logic related to autoplay below.
+ /* eslint-disable jsx-a11y/anchor-is-valid */
+ return (
+ <div
+ className={ className }
+ data-autoplay={ autoplay || null }
+ data-delay={ autoplay ? delay : null }
+ data-effect={ effect }
+ >
+ <div
+ className="wp-block-jetpack-slideshow_container swiper-container"
+ ref={ this.slideshowRef }
+ >
+ <ul className="wp-block-jetpack-slideshow_swiper-wrappper swiper-wrapper">
+ { images.map( ( { alt, caption, id, url } ) => (
+ <li
+ className={ classnames(
+ 'wp-block-jetpack-slideshow_slide',
+ 'swiper-slide',
+ isBlobURL( url ) && 'is-transient'
+ ) }
+ key={ id }
+ >
+ <figure>
+ <img
+ alt={ alt }
+ className={
+ `wp-block-jetpack-slideshow_image wp-image-${ id }` /* wp-image-${ id } makes WordPress add a srcset */
+ }
+ data-id={ id }
+ src={ url }
+ />
+ { isBlobURL( url ) && <Spinner /> }
+ { caption && (
+ <RichText.Content
+ className="wp-block-jetpack-slideshow_caption gallery-caption"
+ tagName="figcaption"
+ value={ caption }
+ />
+ ) }
+ </figure>
+ </li>
+ ) ) }
+ </ul>
+ <a
+ className="wp-block-jetpack-slideshow_button-prev swiper-button-prev swiper-button-white"
+ ref={ this.btnPrevRef }
+ role="button"
+ />
+ <a
+ className="wp-block-jetpack-slideshow_button-next swiper-button-next swiper-button-white"
+ ref={ this.btnNextRef }
+ role="button"
+ />
+ <a
+ aria-label="Pause Slideshow"
+ className="wp-block-jetpack-slideshow_button-pause"
+ role="button"
+ />
+ <div
+ className="wp-block-jetpack-slideshow_pagination swiper-pagination swiper-pagination-white"
+ ref={ this.paginationRef }
+ />
+ </div>
+ </div>
+ );
+ /* eslint-enable jsx-a11y/anchor-is-valid */
+ }
+
+ prefersReducedMotion = () => {
+ return (
+ typeof window !== 'undefined' &&
+ window.matchMedia( '(prefers-reduced-motion: reduce)' ).matches
+ );
+ };
+
+ buildSwiper = ( initialSlide = 0 ) =>
+ // Using refs instead of className-based selectors allows us to
+ // have multiple swipers on one page without collisions, and
+ // without needing to add IDs or the like.
+ createSwiper(
+ this.slideshowRef.current,
+ {
+ autoplay:
+ this.props.autoplay && ! this.prefersReducedMotion()
+ ? {
+ delay: this.props.delay * 1000,
+ disableOnInteraction: false,
+ }
+ : false,
+ effect: this.props.effect,
+ loop: true,
+ initialSlide,
+ navigation: {
+ nextEl: this.btnNextRef.current,
+ prevEl: this.btnPrevRef.current,
+ },
+ pagination: {
+ clickable: true,
+ el: this.paginationRef.current,
+ type: 'bullets',
+ },
+ },
+ {
+ init: swiperInit,
+ imagesReady: swiperResize,
+ paginationRender: swiperPaginationRender,
+ transitionEnd: swiperApplyAria,
+ }
+ );
+}
+
+export default Slideshow;