diff options
Diffstat (limited to 'present/js/controllers/overview.js')
| -rw-r--r-- | present/js/controllers/overview.js | 255 | 
1 files changed, 255 insertions, 0 deletions
| diff --git a/present/js/controllers/overview.js b/present/js/controllers/overview.js new file mode 100644 index 0000000..30e4c63 --- /dev/null +++ b/present/js/controllers/overview.js @@ -0,0 +1,255 @@ +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 ); +				} + +			} +		} + +	} + +}
\ No newline at end of file | 
