infuze-ebook
Version:
Interactive eBook UI including navigation, quiz loading, sidebar menu
689 lines (615 loc) • 20.8 kB
JavaScript
//import "babel-polyfill";
import { $on, qs, $log, $logt } from "./util";
import DocReady from "./windowLoaded";
import Timeline from "./timeline";
import { SCORM } from "pipwerks-scorm-api-wrapper";
//import '../scss/styles.scss'
import Cheerio from 'cheerio';
//import Router from 'es6-router';
//import { Base64 } from 'js-base64';
//DocReady(() => {
//const app = new App();
/* const loadHandler = () => {
app.setNavigationEvents();
app.loadSection();
} */
//$on(window, "load", loadHandler.bind(app));
//$on(window, "onbeforeunload", SCORM.quit);
//$on(window, "onunload", SCORM.quit);
/* const router = new Router({ ... })
.add(() => {
// getPage('/');
})
.add(/about/, () => {
// getPage('about');
})
.add('contact', () => {
// getPage('contact');
});
router.remove('contact');
router.navigate('about'); */
//});
export default class Ebook {
constructor() {
/* $log('Router', Router)
this.router = new Router({})
.add(/about/, () => {
$log('ABOUT')
})
$log('this.router', this.router) */
/* router.add(/about/, () => {
$log('ABOUT')
}) */
this.textElementTimeline;
this.shapeElementTimeline;
this.animationJson = {};
this.throttled = false;
this.showAnimations = true;
this.allSlides = [];
this.currentNodeSelection;
this.display;
this.slidesCurrentPage = 0;
this.slideCount = 0;
this.displayModeBtns = document.getElementsByName("displayMode");
this.displayTypes = [
{
type: 'slides',
container: '.container--layout-1',
prefix: 's',
page: 'slides.html',
selector: 'page-',
button: '#slidesRadio'
},
{
type: 'quiz',
container: '.container--iquiz',
prefix: 'q',
page: 'quiz.html',
selector: 'question-',
button: '#quizRadio'
},
{
type: 'media',
container: '.container--media',
prefix: 'm',
page: 'media.html',
selector: 'media-',
button: '#mediaRadio'
}
]
}
loadSection() {
/* this.router = new Router({})
.add(/about/, () => {
$log('ABOUT')
})
return; */
location.hash = location.hash || "#s0";
const query = /\#(.)(\d+)/.exec(location.hash);
const prefix = query[1];
//this.currentPage = +query[2];
const thisSectionType = this.displayTypes.find(type => type.prefix === prefix);
this.display = thisSectionType.type;
const url = thisSectionType.page,
selector = '.js-wrapper';
$log('****** loadSection ', url);
const content_div = qs(selector);
const xmlHttp = new XMLHttpRequest();
let cFunction = this.setView.bind(this);
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
const $ = Cheerio.load(xmlHttp.responseText);
const content = $(selector).children()
.after($(this).contents())
.remove();
content_div.innerHTML = content;
cFunction(this);
}
};
xmlHttp.open("GET", url, true);
xmlHttp.send(null);
}
setView() {
this.setPageEvents();
this.setStateValues();
this.hidePages();
this.definePages();
this.displayPage();
this.doResize();
this.resetNavigationStates();
SCORM.init();
///ANIME/// this.createAnimationTimelines();
////ANIME/// if (this.showAnimations) this.playTimelines();
}
hashChangedHandler() {
const query = /\#(.)(\d+)/.exec(location.hash);
const prefix = query[1];
this.currentPage = +query[2];
$log('****** hashChangedHandler ', prefix + ":" + query);
if (this.displayTypes.find(type => type.prefix === prefix).type !== this.display) {
this.loadSection();
return;
}
this.setStateValues();
this.hidePages();
this.displayPage();
this.doResize();
this.resetNavigationStates();
this.setPageNumber(this.getPageNumber());
document.querySelector("body").scrollTop = 0;
///ANIME/// if (this.showAnimations) this.createAnimationTimelines();
///ANIME/// if (this.showAnimations) this.playTimelines();
}
definePages() {
//$log('****** this.display ', this.display);
const container = this.displayTypes.find(type => type.type === this.display).container;
[...this.allSlides] = document.querySelectorAll(container);
this.slideCount = this.allSlides.length;
}
hidePages() {
// Set wrapper and pages to hidden
qs(".js-wrapper").classList.add = "hidden";
this.allSlides.forEach(el => {
el.classList.add("hidden");
});
}
setStateValues() {
location.hash = location.hash || "#s0";
const query = /\#(.)(\d+)/.exec(location.hash);
const prefix = query[1];
this.currentPage = +query[2];
const thisSectionType = this.displayTypes.find(type => type.prefix === prefix);
this.display = thisSectionType.type;
qs(thisSectionType.button).checked = true;
}
setPageEvents() {
Array.from(document.querySelectorAll(".js-start-quiz")).forEach(el => {
el.onclick = e => this.startQuiz(e);
});
}
setNavigationEvents() {
location.hash = location.hash || "#s0";
qs(".js-back").onclick = e => this.previousClick();
qs(".js-next").onclick = e => this.nextClick();
///ANIME/// qs(".js-animation input").checked = this.showAnimations;
///ANIME/// qs(".js-animation input").onclick = e => this.toggleAnimation(e);
Array.from(this.displayModeBtns)
.forEach(v => v.addEventListener("change", e => {
this.displayModeChanged(e.currentTarget.value);
}));
$on(window, "hashchange", this.hashChangedHandler.bind(this));
//qs("body").addEventListener("touchmove", this.freezeVp, false);
qs(".l-nav-bar").addEventListener("touchmove", this.preventDefault, false);
qs(".l-header").addEventListener("touchmove", this.preventDefault, false);
$on(window, "resize", this.debounce(e => {
this.doResize();
}, 200));
}
debounce(fn, time) {
//$log('>>>>>>>>>> DEBOUNCE')
let timeout;
return function () {
const functionCall = () => fn.apply(this, arguments);
clearTimeout(timeout);
timeout = setTimeout(functionCall, time);
};
};
displayPage() {
const currentPageNum = this.getPageNumber();
const currentPageNode = this.getPageNode(currentPageNum);
if (!currentPageNode) {
alert('displayPage - No page nodes!');
return;
}
const isLeft = currentPageNode.classList.contains("left"),
isRight = currentPageNode.classList.contains("right");
this.addPageNumber(currentPageNode, currentPageNum);
currentPageNode.classList.remove("hidden");
// Show current page and left or right page
if (isLeft) {
this.getPageNode(currentPageNum + 1).classList.remove("hidden");
this.addPageNumber(this.getPageNode(currentPageNum + 1));
}
if (isRight) {
this.getPageNode(currentPageNum - 1).classList.remove("hidden");
this.addPageNumber(this.getPageNode(currentPageNum - 1));
}
// show wrapper
qs(".js-wrapper").classList.remove("hidden");
}
isNextPageVisible() {
const currentPageNum = this.getPageNumber();
let nextPageNode = this.getPageNode(currentPageNum + 1);
if (
nextPageNode &&
nextPageNode.classList.contains("right") &&
!nextPageNode.classList.contains("hidden")
) {
return true;
} else {
return false;
}
}
doResize() {
$log('****** doResize');
const thisPageNode = this.getPageNode(this.getPageNumber()),
nextPageNode = this.getPageNode(this.getPageNumber(1)),
prevPageNode = this.getPageNode(this.getPageNumber(-1)),
isLeft = thisPageNode.classList.contains("left"),
isRight = thisPageNode.classList.contains("right");
let pageToHide;
if (isLeft) pageToHide = nextPageNode;
if (isRight) pageToHide = prevPageNode;
if (window.innerWidth < 900) {
//TODO SAME AS tablet-landscape-up
if (pageToHide) pageToHide.classList.add("hidden");
} else {
if (pageToHide) pageToHide.classList.remove("hidden");
}
//this.resetNavigationStates();
}
addPageNumber(el, num) {
el.insertAdjacentHTML("beforeend", `<div class="page-number">${num}</div>`);
}
nextClick() {
if (
this.getPageNode(this.getPageNumber()).classList.contains("left") &&
this.getPageNode(this.getPageNumber(1)) &&
!this.getPageNode(this.getPageNumber(1)).classList.contains("hidden")
) {
this.navigateToPage(this.getPageNumber(2));
} else {
this.navigateToPage(this.getPageNumber(1));
}
}
previousClick() {
if (
this.getPageNode(this.getPageNumber()).classList.contains("right") &&
this.getPageNode(this.getPageNumber(-1)) &&
!this.getPageNode(this.getPageNumber(-1)).classList.contains("hidden")
) {
this.navigateToPage(this.getPageNumber(-2));
} else {
this.navigateToPage(this.getPageNumber(-1));
}
}
displayModeChanged(e) {
//Array.from(this.displayModeBtns).forEach(v => v.checked ? console.log(v.getAttribute('value')) : null)
let checkedEl = Array.from(this.displayModeBtns).find(el => {
if (el.checked) return true;
});
this.setPageNumber(0);
const thisSectionType = this.displayTypes.find(type => type.type === checkedEl.value);
location.hash = '#' + thisSectionType.prefix + '0';
}
startQuiz(e) {
//console.log("****** startQuiz ", e.target);
location.hash = '#q0';
this.setPageNumber(0);
document.querySelector('body').scrollTop = 0;
}
navigateToPage(p = 0) {
const thisSectionType = this.displayTypes.find(type => type.type === this.display);
location.hash = '#' + thisSectionType.prefix + p;
this.setPageNumber(p);
document.querySelector('body').scrollTop = 0;
}
setPageNumber(page) {
this.slidesCurrentPage = page;
}
getPageNumber(offset = 0) {
const pagePrefix = this.displayTypes.find(type => type.type === this.display).prefix;
let currentHash = location.hash || "#s0";
return +currentHash.replace("#" + pagePrefix, "") + offset;
}
getPageNode(page) {
//console.log('getPageNode ', page);
const pageNamePrefix = this.displayTypes.find(type => type.type === this.display).selector;
let node = this.allSlides.find(
n => n.id === pageNamePrefix + page
) || null;
return node;
}
updateProgressBar() {
//$log("updateProgressBar quiz ", this.display);
const bar = qs(".nav-bar__progress-bar"),
desc = qs(".nav-bar__progress-txt"),
pageNumOffset = this.isNextPageVisible() ? 2 : 1;
bar.style.width = ((this.getPageNumber() + pageNumOffset) / this.slideCount) * 100 + "%";
desc.textContent = `${this.getPageNumber() + pageNumOffset} / ${this.slideCount}`;
}
resetNavigationStates() {
let thisPageNode = this.getPageNode(this.getPageNumber()),
nextPageNode = this.getPageNode(this.getPageNumber(1)),
prevPageNode = this.getPageNode(this.getPageNumber(-1));
if (prevPageNode) {
if (prevPageNode.classList.contains("left")) {
if (prevPageNode.classList.contains("hidden")) {
enablePrevioust();
} else {
prevPageNode = this.getPageNode(this.getPageNumber(-2));
if (prevPageNode) {
enablePrevioust();
} else {
disablePrevious();
}
}
} else {
enablePrevioust();
}
} else {
disablePrevious();
}
if (nextPageNode) {
if (nextPageNode.classList.contains("right")) {
if (nextPageNode.classList.contains("hidden")) {
enableNext();
} else {
// Already visible
nextPageNode = this.getPageNode(this.getPageNumber(2));
if (nextPageNode) {
enableNext();
} else {
disableNext();
}
}
} else {
enableNext();
}
} else {
disableNext();
}
this.updateProgressBar();
function disablePrevious() {
qs(".js-back").setAttribute("disabled", "");
}
function enablePrevioust() {
qs(".js-back").removeAttribute("disabled");
}
function disableNext() {
qs(".js-next").setAttribute("disabled", "");
}
function enableNext() {
qs(".js-next").removeAttribute("disabled");
}
}
//////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////// TIMELINE ANIMATION ///////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
toggleAnimation(e) {
//console.log('****** toggleAnimation ', e.target.checked);
this.showAnimations = e.target.checked;
}
replayAnimation() {
if (this.textElementTimeline) this.textElementTimeline.replayAnimation();
if (this.shapeElementTimeline) this.shapeElementTimeline.replayAnimation();
}
playTimelines() {
if (this.textElementTimeline) this.textElementTimeline.startAmnimation();
if (this.shapeElementTimeline) this.shapeElementTimeline.startAmnimation();
}
disableNav() {
qs(".js-next").setAttribute("disabled", "");
qs(".js-back").setAttribute("disabled", "");
}
enableNav() {
this.resetNavigationStates();
}
onTimelineStarted(evt) {
//$log(">>>>>>>>>>>>>> onTimelineStarted ", evt);
this.disableNav();
}
onTimelineFinished(evt) {
//$log(">>>>>>>>>>>>>> onTimelineFinished ", evt);
let textComplete = true,
shapesComplete = true;
if (this.textElementTimeline && this.textElementTimeline.status !== 'complete') {
textComplete = false
}
if (this.shapeElementTimeline && this.shapeElementTimeline.status !== 'complete') {
shapesComplete = false
}
if (textComplete && shapesComplete) this.enableNav();
document.querySelector("body").scrollTop = 0;
window.scrollTo(0, 1);
}
createAnimationTimelines() {
//$log(">>>>>>>>>>>>>> createAnimationTimelines");
const defaultDuration = "200",
defaultOffset = "-=50",
currentPageNum = this.getPageNumber(),
currentPageNode = this.getPageNode(this.getPageNumber()),
prevPageNode = this.getPageNode(this.getPageNumber(-1)),
nextPageNode = this.getPageNode(this.getPageNumber(1)),
isLeft = currentPageNode.classList.contains("left"),
isRight = currentPageNode.classList.contains("right");
const pageNamePrefix = this.display === "slides" ? "#page-" : "#question-";
const [...currentPageNodelist] = document.querySelectorAll(pageNamePrefix + currentPageNum + " [data-animate]"),
[...lefttNodelist] = document.querySelectorAll(
pageNamePrefix + (currentPageNum - 1) + " [data-animate]"
),
[...rightNodelist] = document.querySelectorAll(
pageNamePrefix + (currentPageNum + 1) + " [data-animate]"
);
let completeTextNodeList, completeShapeNodeList;
//$log("****************** isLeft ", isLeft);
//$log("****************** isRight ", isRight);
if (
isLeft &&
nextPageNode &&
nextPageNode.classList.contains("right") &&
!nextPageNode.classList.contains("hidden")
) {
// Combine next page nodes
//$log("****************** Combine next page nodes ");
const currentPageTextNodelistSorted = getTextNodes(
currentPageNodelist,
currentPageNum
),
currentPageShapeNodelistSorted = getShapeNodes(
currentPageNodelist,
currentPageNum
),
nextPageTextNodelistSorted = getTextNodes(
rightNodelist,
currentPageNum + 1
),
nextPageShapeNodelistSorted = getShapeNodes(
rightNodelist,
currentPageNum + 1
);
completeTextNodeList = [
...nextPageTextNodelistSorted,
...currentPageTextNodelistSorted
];
completeShapeNodeList = [
...nextPageShapeNodelistSorted,
...currentPageShapeNodelistSorted
];
} else if (
isRight &&
prevPageNode &&
prevPageNode.classList.contains("left") &&
!prevPageNode.classList.contains("hidden")
) {
// Combine previous page nodes
//$log("****************** Combine previous page nodes ");
const currentPageTextNodelistSorted = getTextNodes(
currentPageNodelist,
currentPageNum
),
currentPageShapeNodelistSorted = getShapeNodes(
currentPageNodelist,
currentPageNum
),
previousPageTextNodelistSorted = getTextNodes(
lefttNodelist,
currentPageNum - 1
),
previousPageShapeNodelistSorted = getShapeNodes(
lefttNodelist,
currentPageNum - 1
);
completeTextNodeList = [
...currentPageTextNodelistSorted,
...previousPageTextNodelistSorted
];
completeShapeNodeList = [
...currentPageShapeNodelistSorted,
...previousPageShapeNodelistSorted
];
} else {
// This page nodes only
//$log("****************** This page nodes only ");
completeTextNodeList = getTextNodes(currentPageNodelist, currentPageNum);
completeShapeNodeList = getShapeNodes(
currentPageNodelist,
currentPageNum
);
}
function getTextNodes(nodes, page, counter = 0) {
return nodes
.filter(node => /P|H1|H2|H3|H4|H5|LI|UL|OL|DIV|BUTTON/.test(node.nodeName))
.map(node => {
let step = node.getAttribute("data-animate");
if (!step || step === "*" || step === "") {
counter++;
node.setAttribute("data-animate", counter);
} else {
counter = +step;
}
return node;
})
.sort(sorter)
.reverse()
.map(node => {
node.pageNumber = page;
return node;
});
}
function getShapeNodes(nodes, page) {
return nodes
.filter(node => /FIGURE|IMG/.test(node.nodeName))
.sort(sorter)
.reverse()
.map(node => {
node.pageNumber = page;
return node;
});
}
function sorter(obj1, obj2) {
return obj1.dataset.animate - obj2.dataset.animate;
}
if (completeTextNodeList.length) {
//$logt(completeTextNodeList, 'completeTextNodeList');
this.textElementTimeline = new Timeline(
completeTextNodeList,
this.animationJson
);
this.textElementTimeline.on('started', this.onTimelineStarted.bind(this));
this.textElementTimeline.on('complete', this.onTimelineFinished.bind(this));
this.textElementTimeline.setup();
}
if (completeShapeNodeList.length) {
//$logt(completeShapeNodeList, 'completeShapeNodeList');
this.shapeElementTimeline = new Timeline(
completeShapeNodeList,
this.animationJson
);
this.shapeElementTimeline.on('started', this.onTimelineStarted.bind(this));
this.shapeElementTimeline.on('complete', this.onTimelineFinished.bind(this));
this.shapeElementTimeline.setup();
}
return;
}
loadJSON() { // UNUSED
function getJsonFileName(loc) {
let [fileName, foldername, ...rest] = loc.href.split("/").reverse();
let pathItems = loc.href.split("/");
fileName = pathItems.pop();
let path = pathItems.join("/");
let retPath = path + "/animate.json";
return retPath;
}
function validateResponse(response) {
//console.log('APP: validateResponse: ', response);
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
function readResponseAsJSON(response) {
//console.log('APP: readResponseAsJSON: ', response);
return response.json();
}
function logResult(result) {
//console.log('APP: logResult: ', result);
return result;
}
function logError(error) {
//console.log('Looks like there was a problem: \n', error);
}
function setAminProps(response) {
//console.log('****** setAminProps response', response);
this.animations = response;
}
//console.log('****** loadAnimationSeq start');
return (
fetch(getJsonFileName(window.location), {
headers: { Accept: "application/json" },
credentials: "same-origin"
})
.then(validateResponse)
.then(readResponseAsJSON)
.then(logResult)
//.then(setAminProps)
.then(res => this.continueStartUp(res))
.catch(err => {
logError(err);
this.continueStartUp({});
})
);
}
continueStartUp(json = {}) {
}
}