| 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.Reveal.isScrollView() && !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 ); |
| } |
| |
| } |
| } |
| |
| } |
| |
| } |