vanillajs-deck/js/navigator.js

199 lines
5.5 KiB
JavaScript
Raw Permalink Normal View History

2019-11-24 00:02:31 +00:00
// @ts-check
2019-11-23 00:37:45 +00:00
import { loadSlides } from "./slideLoader.js"
2019-11-24 00:02:31 +00:00
import { Slide } from "./slide.js"
2019-11-23 00:37:45 +00:00
import { Router } from "./router.js"
2019-11-23 20:07:22 +00:00
import { Animator } from "./animator.js"
2019-11-22 19:28:19 +00:00
2019-11-24 00:02:31 +00:00
/**
* The main class that handles rendering the slide decks
2019-11-25 19:34:32 +00:00
* @extends {HTMLElement}
2019-11-24 00:02:31 +00:00
*/
export class Navigator extends HTMLElement {
2019-11-23 00:37:45 +00:00
2019-11-24 00:02:31 +00:00
/**
* Create an instance of the custom navigator element
*/
2019-11-23 00:37:45 +00:00
constructor() {
super();
2019-11-25 19:34:32 +00:00
/**
* The related animation control
* @type {Animator}
*/
2019-11-23 20:07:22 +00:00
this._animator = new Animator();
2019-11-25 19:34:32 +00:00
/**
* The related router control
* @type {Router}
*/
2019-11-23 00:37:45 +00:00
this._router = new Router();
2019-11-25 19:34:32 +00:00
/**
* The last known route
* @type {string}
*/
2019-11-23 00:37:45 +00:00
this._route = this._router.getRoute();
2019-11-25 19:34:32 +00:00
/**
* Custom event raised when the current slide changes
* @type {CustomEvent}
*/
2019-11-23 00:37:45 +00:00
this.slidesChangedEvent = new CustomEvent("slideschanged", {
bubbles: true,
cancelable: false
});
this._router.eventSource.addEventListener("routechanged", () => {
if (this._route !== this._router.getRoute()) {
this._route = this._router.getRoute();
if (this._route) {
const slide = parseInt(this._route) - 1;
2019-11-23 00:37:45 +00:00
this.jumpTo(slide);
2019-11-24 00:02:31 +00:00
}
2019-11-23 00:37:45 +00:00
}
});
}
2019-11-24 00:02:31 +00:00
/**
* Get the list of observed attributes
2019-11-25 19:34:32 +00:00
* @returns {string[]} The list of attributes to watch
2019-11-24 00:02:31 +00:00
*/
2019-11-23 00:37:45 +00:00
static get observedAttributes() {
return ["start"];
}
2019-11-24 00:02:31 +00:00
/**
* Called when an attribute changes
* @param {string} attrName
* @param {string} oldVal
* @param {string} newVal
*/
2019-11-23 00:37:45 +00:00
async attributeChangedCallback(attrName, oldVal, newVal) {
if (attrName === "start") {
if (oldVal !== newVal) {
this._slides = await loadSlides(newVal);
this._route = this._router.getRoute();
var slide = 0;
if (this._route) {
slide = parseInt(this._route) - 1;
}
this.jumpTo(slide);
2019-11-23 20:28:04 +00:00
this._title = document.querySelectorAll("title")[0];
2019-11-23 00:37:45 +00:00
}
}
2019-11-22 19:28:19 +00:00
}
2019-11-24 00:02:31 +00:00
/**
* Current slide index
2019-11-25 19:34:32 +00:00
* @returns {number} The current slide index
2019-11-24 00:02:31 +00:00
*/
2019-11-22 19:28:19 +00:00
get currentIndex() {
return this._currentIndex;
}
2019-11-24 00:02:31 +00:00
/**
* Current slide
2019-11-25 19:34:32 +00:00
* @returns {Slide} The current slide
2019-11-24 00:02:31 +00:00
*/
2019-11-22 19:28:19 +00:00
get currentSlide() {
2019-11-23 00:37:45 +00:00
return this._slides ? this._slides[this._currentIndex] : null;
2019-11-22 19:28:19 +00:00
}
2019-11-24 00:02:31 +00:00
/**
* Total number of slides
2019-11-25 19:34:32 +00:00
* @returns {number} The total slide count
2019-11-24 00:02:31 +00:00
*/
2019-11-22 19:28:19 +00:00
get totalSlides() {
2019-11-23 00:37:45 +00:00
return this._slides ? this._slides.length : 0;
2019-11-22 19:28:19 +00:00
}
2019-11-24 00:02:31 +00:00
/**
* True if a previous slide exists
2019-11-25 19:34:32 +00:00
* @returns {boolean} True if a previous slide exists
2019-11-24 00:02:31 +00:00
*/
2019-11-22 19:28:19 +00:00
get hasPrevious() {
return this._currentIndex > 0;
}
2019-11-24 00:02:31 +00:00
/**
* True if a next slide exists
2019-11-25 19:34:32 +00:00
* @returns {boolean} True if a subsequent slide exists
2019-11-24 00:02:31 +00:00
*/
2019-11-22 19:28:19 +00:00
get hasNext() {
2019-11-26 17:51:45 +00:00
const host = this.querySelector("div");
2019-11-26 20:05:41 +00:00
if (host) {
const appear = host.querySelectorAll(".appear");
if (appear && appear.length) {
return true;
}
2019-11-26 17:51:45 +00:00
}
2019-11-22 19:28:19 +00:00
return this._currentIndex < (this.totalSlides - 1);
}
2019-11-24 00:02:31 +00:00
/**
* Main slide navigation: jump to specific slide
2019-11-25 19:34:32 +00:00
* @param {number} slideIdx The index of the slide to navigate to
2019-11-24 00:02:31 +00:00
*/
2019-11-22 19:28:19 +00:00
jumpTo(slideIdx) {
2019-11-23 20:07:22 +00:00
if (this._animator.transitioning) {
return;
}
2019-11-22 19:28:19 +00:00
if (slideIdx >= 0 && slideIdx < this.totalSlides) {
this._currentIndex = slideIdx;
2019-11-23 00:37:45 +00:00
this.innerHTML = '';
this.appendChild(this.currentSlide.html);
2019-11-24 00:02:31 +00:00
this._router.setRoute((slideIdx + 1).toString());
2019-11-23 00:37:45 +00:00
this._route = this._router.getRoute();
2019-11-24 00:02:31 +00:00
document.title = `${this.currentIndex + 1}/${this.totalSlides}: ${this.currentSlide.title}`;
2019-11-23 00:37:45 +00:00
this.dispatchEvent(this.slidesChangedEvent);
2019-11-23 20:07:22 +00:00
if (this._animator.animationReady) {
this._animator.endAnimation(this.querySelector("div"));
}
2019-11-22 19:28:19 +00:00
}
}
/**
* Check for in-slide appearances on navigation
* @returns {boolean} True if an element was revealed
*/
checkForAppears() {
const host = this.querySelector("div");
const appear = host.querySelectorAll(".appear");
if (appear.length) {
appear[0].classList.remove("appear");
return true;
}
return false;
}
2019-11-24 00:02:31 +00:00
/**
* Advance to next slide, if it exists. Applies animation if transition is specified
*/
2019-11-22 19:28:19 +00:00
next() {
if (this.checkForAppears()) {
2019-11-26 17:51:45 +00:00
this.dispatchEvent(this.slidesChangedEvent);
return;
}
2019-11-22 19:28:19 +00:00
if (this.hasNext) {
2019-11-23 20:07:22 +00:00
if (this.currentSlide.transition !== null) {
this._animator.beginAnimation(
2019-11-24 00:02:31 +00:00
this.currentSlide.transition,
2019-11-23 20:07:22 +00:00
this.querySelector("div"),
2019-11-24 00:02:31 +00:00
() => this.jumpTo(this.currentIndex + 1));
2019-11-23 20:07:22 +00:00
}
else {
this.jumpTo(this.currentIndex + 1);
}
2019-11-22 19:28:19 +00:00
}
}
2019-11-24 00:02:31 +00:00
/**
* Move to previous slide, if it exists
*/
2019-11-22 19:28:19 +00:00
previous() {
if (this.hasPrevious) {
this.jumpTo(this.currentIndex - 1);
}
}
2019-11-23 00:37:45 +00:00
}
2019-11-24 00:02:31 +00:00
/**
* Register the custom slide-deck component
*/
2019-11-23 00:37:45 +00:00
export const registerDeck = () => customElements.define('slide-deck', Navigator);