import { SLIDES_SELECTOR } from '../utils/constants.js' import { extend, queryAll, transformElement } from '../utils/util.js' /** * Handles all logic related to the overview mode * (birds-eye view of all slides). */ export default class Overview { constructor( Reveal ) { this.Reveal = Reveal; this.active = false; this.onSlideClicked = this.onSlideClicked.bind( this ); } /** * Displays the overview of slides (quick nav) by scaling * down and arranging all slide elements. */ activate() { // Only proceed if enabled in config if( this.Reveal.getConfig().overview && !this.isActive() ) { this.active = true; this.Reveal.getRevealElement().classList.add( 'overview' ); // Don't auto-slide while in overview mode this.Reveal.cancelAutoSlide(); // Move the backgrounds element into the slide container to // that the same scaling is applied this.Reveal.getSlidesElement().appendChild( this.Reveal.getBackgroundsElement() ); // Clicking on an overview slide navigates to it queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => { if( !slide.classList.contains( 'stack' ) ) { slide.addEventListener( 'click', this.onSlideClicked, true ); } } ); // Calculate slide sizes const margin = 70; const slideSize = this.Reveal.getComputedSlideSize(); this.overviewSlideWidth = slideSize.width + margin; this.overviewSlideHeight = slideSize.height + margin; // Reverse in RTL mode if( this.Reveal.getConfig().rtl ) { this.overviewSlideWidth = -this.overviewSlideWidth; } this.Reveal.updateSlidesVisibility(); this.layout(); this.update(); this.Reveal.layout(); const indices = this.Reveal.getIndices(); // Notify observers of the overview showing this.Reveal.dispatchEvent({ type: 'overviewshown', data: { 'indexh': indices.h, 'indexv': indices.v, 'currentSlide': this.Reveal.getCurrentSlide() } }); } } /** * Uses CSS transforms to position all slides in a grid for * display inside of the overview mode. */ layout() { // Layout slides this.Reveal.getHorizontalSlides().forEach( ( hslide, h ) => { hslide.setAttribute( 'data-index-h', h ); transformElement( hslide, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' ); if( hslide.classList.contains( 'stack' ) ) { queryAll( hslide, 'section' ).forEach( ( vslide, v ) => { vslide.setAttribute( 'data-index-h', h ); vslide.setAttribute( 'data-index-v', v ); transformElement( vslide, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' ); } ); } } ); // Layout slide backgrounds Array.from( this.Reveal.getBackgroundsElement().childNodes ).forEach( ( hbackground, h ) => { transformElement( hbackground, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' ); queryAll( hbackground, '.slide-background' ).forEach( ( vbackground, v ) => { transformElement( vbackground, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' ); } ); } ); } /** * Moves the overview viewport to the current slides. * Called each time the current slide changes. */ update() { const vmin = Math.min( window.innerWidth, window.innerHeight ); const scale = Math.max( vmin / 5, 150 ) / vmin; const indices = this.Reveal.getIndices(); this.Reveal.transformSlides( { overview: [ 'scale('+ scale +')', 'translateX('+ ( -indices.h * this.overviewSlideWidth ) +'px)', 'translateY('+ ( -indices.v * this.overviewSlideHeight ) +'px)' ].join( ' ' ) } ); } /** * Exits the slide overview and enters the currently * active slide. */ deactivate() { // Only proceed if enabled in config if( this.Reveal.getConfig().overview ) { this.active = false; this.Reveal.getRevealElement().classList.remove( 'overview' ); // Temporarily add a class so that transitions can do different things // depending on whether they are exiting/entering overview, or just // moving from slide to slide this.Reveal.getRevealElement().classList.add( 'overview-deactivating' ); setTimeout( () => { this.Reveal.getRevealElement().classList.remove( 'overview-deactivating' ); }, 1 ); // Move the background element back out this.Reveal.getRevealElement().appendChild( this.Reveal.getBackgroundsElement() ); // Clean up changes made to slides queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => { transformElement( slide, '' ); slide.removeEventListener( 'click', this.onSlideClicked, true ); } ); // Clean up changes made to backgrounds queryAll( this.Reveal.getBackgroundsElement(), '.slide-background' ).forEach( background => { transformElement( background, '' ); } ); this.Reveal.transformSlides( { overview: '' } ); const indices = this.Reveal.getIndices(); this.Reveal.slide( indices.h, indices.v ); this.Reveal.layout(); this.Reveal.cueAutoSlide(); // Notify observers of the overview hiding this.Reveal.dispatchEvent({ type: 'overviewhidden', data: { 'indexh': indices.h, 'indexv': indices.v, 'currentSlide': this.Reveal.getCurrentSlide() } }); } } /** * Toggles the slide overview mode on and off. * * @param {Boolean} [override] Flag which overrides the * toggle logic and forcibly sets the desired state. True means * overview is open, false means it's closed. */ toggle( override ) { if( typeof override === 'boolean' ) { override ? this.activate() : this.deactivate(); } else { this.isActive() ? this.deactivate() : this.activate(); } } /** * Checks if the overview is currently active. * * @return {Boolean} true if the overview is active, * false otherwise */ isActive() { return this.active; } /** * Invoked when a slide is and we're in the overview. * * @param {object} event */ onSlideClicked( event ) { if( this.isActive() ) { event.preventDefault(); let element = event.target; while( element && !element.nodeName.match( /section/gi ) ) { element = element.parentNode; } if( element && !element.classList.contains( 'disabled' ) ) { this.deactivate(); if( element.nodeName.match( /section/gi ) ) { let h = parseInt( element.getAttribute( 'data-index-h' ), 10 ), v = parseInt( element.getAttribute( 'data-index-v' ), 10 ); this.Reveal.slide( h, v ); } } } } }