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); } } } } }