diff options
Diffstat (limited to 'plugins/jetpack/extensions/blocks/slideshow/slideshow.js')
-rw-r--r-- | plugins/jetpack/extensions/blocks/slideshow/slideshow.js | 232 |
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; |