Annotation-System/hmml_to_html/player.js

1225 lines
44 KiB
JavaScript

// refsCallback: (optional)
// Will be called when the player enters a marker that has a `data-ref` attribute. The value of `data-ref` will be passed to the function.
// When leaving a marker that a `data-ref` attribute, and entering a marker without one (or not entering a new marker at all), the function will be called with `null`.
function Player(htmlContainer, refsCallback) {
this.container = htmlContainer;
this.markersContainer = this.container.querySelector(".markers_container");
this.videoContainer = this.container.querySelector(".video_container");
this.refsCallback = refsCallback || function() {};
if (!this.videoContainer.getAttribute("data-videoId")) {
console.error("Expected to find data-videoId attribute on", this.videoContainer, "for player initialized on", this.container);
throw new Error("Missing data-videoId attribute.");
}
this.markers = [];
var markerEls = this.markersContainer.querySelectorAll(".marker");
if (markerEls.length == 0) {
console.error("No markers found in", this.markersContainer, "for player initialized on", this.container);
throw new Error("Missing markers.");
}
for (var i = 0; i < markerEls.length; ++i) {
var marker = {
timestamp: parseInt(markerEls[i].getAttribute("data-timestamp"), 10),
ref: markerEls[i].getAttribute("data-ref"),
endTime: (i < markerEls.length - 1 ? parseInt(markerEls[i+1].getAttribute("data-timestamp"), 10) : null),
el: markerEls[i],
fadedProgress: markerEls[i].querySelector(".progress.faded"),
progress: markerEls[i].querySelector(".progress.main"),
hoverx: null
};
marker.el.addEventListener("click", this.onMarkerClick.bind(this, marker));
marker.el.addEventListener("mousemove", this.onMarkerMouseMove.bind(this, marker));
marker.el.addEventListener("mouseleave", this.onMarkerMouseLeave.bind(this, marker));
this.markers.push(marker);
}
this.currentMarker = null;
this.currentMarkerIdx = null;
this.youtubePlayer = null;
this.youtubePlayerReady = false;
this.playing = false;
this.shouldPlay = false;
this.buffering = false;
this.pauseAfterBuffer = false;
this.speed = 1;
this.currentTime = 0;
this.lastFrameTime = 0;
this.scrollTo = -1;
this.scrollPosition = 0;
this.nextFrame = null;
this.looping = false;
this.markersContainer.addEventListener("wheel", function(ev) {
this.scrollTo = -1;
}.bind(this));
Player.initializeYoutube(this.onYoutubeReady.bind(this));
this.updateSize();
this.resume();
}
// Start playing the video from the current position.
// If the player hasn't loaded yet, it will autoplay when ready.
Player.prototype.play = function() {
if (this.youtubePlayerReady) {
if (!this.playing) {
this.youtubePlayer.playVideo();
}
this.pauseAfterBuffer = false;
} else {
this.shouldPlay = true;
}
};
// Pause the video at the current position.
// If the player hasn't loaded yet, it will not autoplay when ready. (This is the default)
Player.prototype.pause = function() {
if (this.youtubePlayerReady) {
if (this.playing) {
this.youtubePlayer.pauseVideo();
} else if (this.buffering) {
this.pauseAfterBuffer = true;
}
} else {
this.shouldPlay = false;
}
};
// Sets the current time. Does not affect play status.
// If the player hasn't loaded yet, it will seek to this time when ready.
Player.prototype.setTime = function(time) {
this.currentTime = time;
if (this.youtubePlayerReady) {
this.currentTime = Math.max(0, Math.min(this.currentTime, this.youtubePlayer.getDuration()));
this.youtubePlayer.seekTo(this.currentTime);
}
this.updateProgress();
};
Player.prototype.jumpToNextMarker = function() {
var targetMarkerIdx = Math.min((this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx + 1), this.markers.length-1);
var targetTime = this.markers[targetMarkerIdx].timestamp;
this.setTime(targetTime);
this.play();
};
Player.prototype.jumpToPrevMarker = function() {
var targetMarkerIdx = Math.max(0, (this.currentMarkerIdx === null ? 0 : this.currentMarkerIdx - 1));
var targetTime = this.markers[targetMarkerIdx].timestamp;
this.setTime(targetTime);
this.play();
};
// Call this after changing the size of the video container in order to update the youtube player.
Player.prototype.updateSize = function() {
var width = this.videoContainer.offsetWidth;
var height = width / 16 * 9;
this.markersContainer.style.height = height;
if (this.youtubePlayerReady) {
this.youtubePlayer.setSize(Math.floor(width), Math.floor(height));
}
}
// Stops the per-frame work that the player does. Call when you want to hide or get rid of the player.
Player.prototype.halt = function() {
this.pause();
this.looping = false;
if (this.nextFrame) {
cancelAnimationFrame(this.nextFrame);
this.nextFrame = null;
}
}
// Resumes the per-frame work that the player does. Call when you want to show the player again after hiding.
Player.prototype.resume = function() {
this.looping = true;
if (!this.nextFrame) {
this.doFrame();
}
}
Player.initializeYoutube = function(callback) {
if (window.APYoutubeAPIReady === undefined) {
window.APYoutubeAPIReady = false;
window.APCallbacks = (callback ? [callback] : []);
window.onYouTubeIframeAPIReady = function() {
window.APYoutubeAPIReady = true;
for (var i = 0; i < APCallbacks.length; ++i) {
APCallbacks[i]();
}
};
var scriptTag = document.createElement("SCRIPT");
scriptTag.setAttribute("type", "text/javascript");
scriptTag.setAttribute("src", "https://www.youtube.com/iframe_api");
document.body.appendChild(scriptTag);
} else if (window.APYoutubeAPIReady === false) {
window.APCallbacks.push(callback);
} else if (window.APYoutubeAPIReady === true) {
callback();
}
}
// END PUBLIC INTERFACE
Player.prototype.onMarkerClick = function(marker, ev) {
var time = marker.timestamp;
if (this.currentMarker == marker && marker.hoverx !== null) {
time += (marker.endTime - marker.timestamp) * marker.hoverx;
}
this.setTime(time);
this.play();
};
function getElementXOffsetFromPage(el) {
var left = 0;
do {
left += el.offsetLeft;
} while (el = el.offsetParent);
return left;
}
Player.prototype.onMarkerMouseMove = function(marker, ev) {
if (this.currentMarker == marker) {
marker.hoverx = (ev.pageX - getElementXOffsetFromPage(marker.el)) / marker.el.offsetWidth;
}
};
Player.prototype.onMarkerMouseLeave = function(marker, ev) {
marker.hoverx = null;
};
Player.prototype.updateProgress = function() {
var prevMarker = this.currentMarker;
this.currentMarker = null;
this.currentMarkerIdx = null;
for (var i = 0; i < this.markers.length; ++i) {
var marker = this.markers[i];
if (marker.timestamp <= this.currentTime && this.currentTime < marker.endTime) {
this.currentMarker = marker;
this.currentMarkerIdx = i;
break;
}
}
if (this.currentMarker) {
var totalWidth = this.currentMarker.el.offsetWidth;
var progress = (this.currentTime - this.currentMarker.timestamp) / (this.currentMarker.endTime - this.currentMarker.timestamp);
if (this.currentMarker.hoverx === null) {
var pixelWidth = progress * totalWidth;
this.currentMarker.fadedProgress.style.width = Math.ceil(pixelWidth) + "px";
this.currentMarker.fadedProgress.style.opacity = pixelWidth - Math.floor(pixelWidth);
this.currentMarker.progress.style.width = Math.floor(pixelWidth) + "px";
} else {
this.currentMarker.fadedProgress.style.opacity = 1;
this.currentMarker.progress.style.width = Math.floor(Math.min(this.currentMarker.hoverx, progress) * totalWidth) + "px";
this.currentMarker.fadedProgress.style.width = Math.floor(Math.max(this.currentMarker.hoverx, progress) * totalWidth) + "px";
}
}
if (this.currentMarker != prevMarker) {
if (prevMarker) {
prevMarker.el.classList.remove("current");
prevMarker.fadedProgress.style.width = "0px";
prevMarker.progress.style.width = "0px";
prevMarker.hoverx = null;
}
if (this.currentMarker) {
this.currentMarker.el.classList.add("current");
this.scrollTo = this.currentMarker.el.offsetTop + this.currentMarker.el.offsetHeight/2.0;
this.scrollPosition = this.markersContainer.scrollTop;
}
if (this.currentMarker) {
this.refsCallback(this.currentMarker.ref, this.currentMarker.el);
} else if (prevMarker && prevMarker.ref) {
this.refsCallback(null);
}
}
};
Player.prototype.doFrame = function() {
var now = performance.now();
var delta = (now - this.lastFrameTime) / 1000.0;
this.lastFrameTime = now;
if (this.playing) {
this.currentTime += delta * this.speed;
}
this.updateProgress();
if (this.scrollTo >= 0) {
var targetPosition = this.scrollTo - this.markersContainer.offsetHeight/2.0;
targetPosition = Math.max(0, Math.min(targetPosition, this.markersContainer.scrollHeight - this.markersContainer.offsetHeight));
this.scrollPosition += (targetPosition - this.scrollPosition) * 0.1;
if (Math.abs(this.scrollPosition - targetPosition) < 1.0) {
this.markersContainer.scrollTop = targetPosition;
this.scrollTo = -1;
} else {
this.markersContainer.scrollTop = this.scrollPosition;
}
}
this.nextFrame = requestAnimationFrame(this.doFrame.bind(this));
};
Player.prototype.onYoutubePlayerReady = function() {
this.youtubePlayerReady = true;
this.markers[this.markers.length-1].endTime = this.youtubePlayer.getDuration();
this.updateSize();
this.youtubePlayer.setPlaybackQuality("hd1080");
if (this.currentTime > 0) {
this.currentTime = Math.max(0, Math.min(this.currentTime, this.youtubePlayer.getDuration()));
this.youtubePlayer.seekTo(this.currentTime, true);
}
if (this.shouldPlay) {
this.youtubePlayer.playVideo();
}
};
Player.prototype.onYoutubePlayerStateChange = function(ev) {
if (ev.data == YT.PlayerState.PLAYING) {
this.playing = true;
this.currentTime = this.youtubePlayer.getCurrentTime();
} else if (ev.data == YT.PlayerState.PAUSED || ev.data == YT.PlayerState.BUFFERING) {
this.playing = false;
this.currentTime = this.youtubePlayer.getCurrentTime();
this.updateProgress();
} else {
this.playing = false;
}
this.buffering = ev.data == YT.PlayerState.BUFFERING;
if (this.playing && this.pauseAfterBuffer) {
this.pauseAfterBuffering = false;
this.pause();
}
};
Player.prototype.onYoutubePlayerPlaybackRateChange = function(ev) {
this.speed = ev.data;
};
Player.prototype.onYoutubeReady = function() {
var youtubePlayerDiv = document.createElement("DIV");
youtubePlayerDiv.id = "youtube_player_" + Player.youtubePlayerCount++;
this.videoContainer.appendChild(youtubePlayerDiv);
this.youtubePlayer = new YT.Player(youtubePlayerDiv.id, {
videoId: this.videoContainer.getAttribute("data-videoId"),
width: this.videoContainer.offsetWidth,
height: this.videoContainer.offsetWidth / 16 * 9,
//playerVars: { disablekb: 1 },
events: {
"onReady": this.onYoutubePlayerReady.bind(this),
"onStateChange": this.onYoutubePlayerStateChange.bind(this),
"onPlaybackRateChange": this.onYoutubePlayerPlaybackRateChange.bind(this)
}
});
};
Player.youtubePlayerCount = 0;
// NOTE(matt): Hereafter is my stuff. Beware!
function toggleMenuVisibility(element) {
if(element.classList.contains("visible"))
{
element.classList.remove("visible");
element.parentNode.classList.remove("visible");
focusedElement.classList.remove("focused");
focusedElement = null;
if(focusedIdentifier)
{
focusedIdentifier.classList.remove("focused");
focusedIdentifier = null;
}
}
else
{
for(menuIndex in menuState)
{
menuState[menuIndex].classList.remove("visible");
menuState[menuIndex].parentNode.classList.remove("visible");
if(focusedElement)
{
focusedElement.classList.remove("focused");
}
if(focusedIdentifier)
{
focusedIdentifier.classList.remove("focused");
}
}
element.classList.add("visible");
element.parentNode.classList.add("visible");
if(element.classList.contains("quotes_container"))
{
if(!lastFocusedQuote)
{
lastFocusedQuote = element.querySelectorAll(".ref")[0];
}
focusedElement = lastFocusedQuote;
focusedElement.classList.add("focused");
}
else if(element.classList.contains("references_container"))
{
if(!lastFocusedReference || !lastFocusedIdentifier)
{
lastFocusedReference = element.querySelectorAll(".ref")[0];
lastFocusedIdentifier = lastFocusedReference.querySelector(".ref_indices").firstElementChild;
}
focusedElement = lastFocusedReference;
focusedElement.classList.add("focused");
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
}
else if(element.classList.contains("filter_container"))
{
if(!lastFocusedCategory)
{
lastFocusedCategory = element.querySelectorAll(".filter_content")[0];
}
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
}
else if(element.classList.contains("credits_container"))
{
if(!lastFocusedCreditItem)
{
if(element.querySelectorAll(".credit .support")[0])
{
lastFocusedCreditItem = element.querySelectorAll(".credit .support")[0];
}
else
{
lastFocusedCreditItem = element.querySelectorAll(".credit .person")[0];
}
}
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
}
}
}
function handleKey(key) {
var gotKey = true;
switch (key) {
case "q": {
if(quotesMenu)
{
toggleMenuVisibility(quotesMenu)
}
} break;
case "r": {
if(referencesMenu)
{
toggleMenuVisibility(referencesMenu)
}
} break;
case "f": {
if(filterMenu)
{
toggleMenuVisibility(filterMenu)
}
} break;
case "c": {
if(creditsMenu)
{
toggleMenuVisibility(creditsMenu)
}
} break;
case "Enter": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("quotes_container"))
{
var time = focusedElement.querySelector(".timecode").getAttribute("data-timestamp");
player.setTime(parseInt(time, 10));
player.play();
}
else if(focusedElement.parentNode.classList.contains("references_container"))
{
var time = focusedIdentifier.getAttribute("data-timestamp");
player.setTime(parseInt(time, 10));
player.play();
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.hasAttribute)
{
var url = focusedElement.getAttribute("href");
window.open(url, "_blank");
}
}
}
else
{
console.log("TODO(matt): Implement me, perhaps?\n");
}
} break;
case "o": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("references_container"))
{
var url = focusedElement.getAttribute("href");
window.open(url, "_blank");
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.hasAttribute("href"))
{
var url = focusedElement.getAttribute("href");
window.open(url, "_blank");
}
}
}
} break;
case "w": case "k": case "ArrowUp": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("quotes_container"))
{
if(focusedElement.previousElementSibling)
{
focusedElement.classList.remove("focused");
lastFocusedQuote = focusedElement.previousElementSibling;
focusedElement = lastFocusedQuote;
focusedElement.classList.add("focused");
}
}
else if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedElement.previousElementSibling)
{
focusedElement.classList.remove("focused");
focusedIdentifier.classList.remove("focused");
lastFocusedReference = focusedElement.previousElementSibling;
focusedElement = lastFocusedReference;
focusedElement.classList.add("focused");
lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
}
}
else if(focusedElement.parentNode.parentNode.classList.contains("filters"))
{
if(focusedElement.previousElementSibling &&
focusedElement.previousElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
lastFocusedCategory = focusedElement.previousElementSibling;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.parentNode.previousElementSibling)
{
focusedElement.classList.remove("focused");
if(focusedElement.parentNode.previousElementSibling.querySelector(".support") &&
focusedElement.classList.contains("support"))
{
lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".support");
focusedElement = lastFocusedCreditItem;
}
else
{
lastFocusedCreditItem = focusedElement.parentNode.previousElementSibling.querySelector(".person");
focusedElement = lastFocusedCreditItem;
}
focusedElement.classList.add("focused");
}
}
}
} break;
case "s": case "j": case "ArrowDown": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("quotes_container"))
{
if(focusedElement.nextElementSibling)
{
focusedElement.classList.remove("focused");
lastFocusedQuote = focusedElement.nextElementSibling;
focusedElement = lastFocusedQuote;
focusedElement.classList.add("focused");
}
}
else if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedElement.nextElementSibling)
{
focusedElement.classList.remove("focused");
focusedIdentifier.classList.remove("focused");
lastFocusedReference = focusedElement.nextElementSibling;
focusedElement = lastFocusedReference;
focusedElement.classList.add("focused");
lastFocusedIdentifier = focusedElement.querySelector(".ref_indices").firstElementChild;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
}
}
else if(focusedElement.parentNode.parentNode.classList.contains("filters"))
{
if(focusedElement.nextElementSibling &&
focusedElement.nextElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
lastFocusedCategory = focusedElement.nextElementSibling;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.parentNode.nextElementSibling)
{
focusedElement.classList.remove("focused");
if(focusedElement.parentNode.nextElementSibling.querySelector(".support") &&
focusedElement.classList.contains("support"))
{
lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".support");
focusedElement = lastFocusedCreditItem;
}
else
{
lastFocusedCreditItem = focusedElement.parentNode.nextElementSibling.querySelector(".person");
focusedElement = lastFocusedCreditItem;
}
focusedElement.classList.add("focused");
}
}
}
} break;
case "a": case "h": case "ArrowLeft": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedIdentifier.previousElementSibling)
{
focusedIdentifier.classList.remove("focused");
lastFocusedIdentifier = focusedIdentifier.previousElementSibling;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
}
}
else if(focusedElement.classList.contains("filter_content"))
{
if(focusedElement.parentNode.classList.contains("filter_media") &&
focusedElement.parentNode.previousElementSibling)
{
focusedElement.classList.remove("focused");
lastFocusedMedium = focusedElement;
if(!lastFocusedTopic)
{
lastFocusedTopic = focusedElement.parentNode.previousElementSibling.children[1];
}
lastFocusedCategory = lastFocusedTopic;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.classList.contains("support"))
{
focusedElement.classList.remove("focused");
lastFocusedCreditItem = focusedElement.previousElementSibling;
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
}
}
}
} break;
case "d": case "l": case "ArrowRight": {
if(focusedElement)
{
if(focusedElement.parentNode.classList.contains("references_container"))
{
if(focusedIdentifier.nextElementSibling)
{
focusedIdentifier.classList.remove("focused");
lastFocusedIdentifier = focusedIdentifier.nextElementSibling;
focusedIdentifier = lastFocusedIdentifier;
focusedIdentifier.classList.add("focused");
}
}
else if(focusedElement.classList.contains("filter_content"))
{
if(focusedElement.parentNode.classList.contains("filter_topics") &&
focusedElement.parentNode.nextElementSibling)
{
focusedElement.classList.remove("focused");
lastFocusedTopic = focusedElement;
if(!lastFocusedMedium)
{
lastFocusedMedium = focusedElement.parentNode.nextElementSibling.children[1];
}
lastFocusedCategory = lastFocusedMedium;
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
}
}
else if(focusedElement.parentNode.classList.contains("credit"))
{
if(focusedElement.classList.contains("person") &&
focusedElement.nextElementSibling)
{
focusedElement.classList.remove("focused");
lastFocusedCreditItem = focusedElement.nextElementSibling;
focusedElement = lastFocusedCreditItem;
focusedElement.classList.add("focused");
}
}
}
} break;
case "x": case " ": {
if(focusedElement && focusedElement.classList.contains("filter_content"))
{
filterItemToggle(focusedElement);
if(focusedElement.nextElementSibling &&
focusedElement.nextElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
if(focusedElement.parentNode.classList.contains("filter_topics"))
{
lastFocusedTopic = focusedElement.nextElementSibling;
lastFocusedCategory = lastFocusedTopic;
}
else
{
lastFocusedMedium = focusedElement.nextElementSibling;
lastFocusedCategory = lastFocusedMedium;
}
lastFocusedElement = lastFocusedCategory;
focusedElement = lastFocusedElement;
focusedElement.classList.add("focused");
}
}
} break;
case "X": case "capitalSpace": {
if(focusedElement && focusedElement.classList.contains("filter_content"))
{
filterItemToggle(focusedElement);
if(focusedElement.previousElementSibling &&
focusedElement.previousElementSibling.classList.contains("filter_content"))
{
focusedElement.classList.remove("focused");
if(focusedElement.parentNode.classList.contains("filter_topics"))
{
lastFocusedTopic = focusedElement.previousElementSibling;
lastFocusedCategory = lastFocusedTopic;
}
else
{
lastFocusedMedium = focusedElement.previousElementSibling;
lastFocusedCategory = lastFocusedMedium;
}
lastFocusedElement = lastFocusedCategory;
focusedElement = lastFocusedElement;
focusedElement.classList.add("focused");
}
}
} break;
case "z": {
toggleFilterMode();
} break;
case "v": {
if(focusedElement && focusedElement.classList.contains("filter_content"))
{
invertFilter(focusedElement)
}
} break;
case "V": {
resetFilter();
} break;
case "?": {
helpDocumentation.classList.toggle("visible");
} break;
case 'N':
case 'D':
case 'S': {
player.jumpToNextMarker();
} break;
case 'P':
case 'A':
case 'W': {
player.jumpToPrevMarker();
} break;
default: {
gotKey = false;
} break;
}
return gotKey;
}
function applyFilter() {
if(filterMode == "exclusive")
{
for(var i = 0; i < testMarkers.length; ++i)
{
var testCategories = testMarkers[i].classList;
for(var j = 0; j < testCategories.length; ++j)
{
if((testCategories[j].startsWith("off_")) && !testMarkers[i].classList.contains("skip"))
{
testMarkers[i].classList.add("skip");
}
}
}
}
else
{
for(var i = 0; i < testMarkers.length; ++i)
{
var testCategories = testMarkers[i].classList;
for(var j = 0; j < testCategories.length; ++j)
{
if((testCategories[j] in filterState || testCategories[j].startsWith("cat_")) && testMarkers[i].classList.contains("skip"))
{
testMarkers[i].classList.remove("skip");
}
}
}
}
}
function toggleFilterMode() {
if(filterMode == "inclusive")
{
filterModeElement.classList.remove("inclusive");
filterModeElement.classList.add("exclusive");
filterMode = "exclusive";
}
else
{
filterModeElement.classList.remove("exclusive");
filterModeElement.classList.add("inclusive");
filterMode = "inclusive";
}
applyFilter();
}
function filterItemToggle(filterItem) {
var selectedCategory = filterItem.classList[1];
filterState[selectedCategory].off = !filterState[selectedCategory].off;
if(filterState[selectedCategory].off)
{
filterItem.classList.add("off");
if(!filterItem.parentNode.classList.contains("filter_media"))
{
filterItem.querySelector(".icon").style.backgroundColor = "transparent";
}
var testMarkers = document.querySelectorAll(".marker." + selectedCategory + ", .marker.cat_" + selectedCategory);
for(var j = 0; j < testMarkers.length; ++j)
{
if(filterState[selectedCategory].type == "topic")
{
testMarkers[j].classList.remove("cat_" + selectedCategory);
testMarkers[j].classList.add("off_" + selectedCategory);
var markerCategories = testMarkers[j].querySelectorAll(".category." + selectedCategory);
for(var k = 0; k < markerCategories.length; ++k)
{
if(markerCategories[k].classList.contains(selectedCategory))
{
markerCategories[k].classList.add("off");
markerCategories[k].style.backgroundColor = "transparent";
}
}
}
else
{
testMarkers[j].classList.remove(selectedCategory);
testMarkers[j].classList.add("off_" + selectedCategory);
}
Skipping = 1;
if(filterMode == "exclusive")
{
testMarkers[j].classList.add("skip");
}
else
{
var markerClasses = testMarkers[j].classList;
for(var k = 0; k < markerClasses.length; ++k)
{
if(markerClasses[k] in filterState || markerClasses[k].replace(/^cat_/, "") in filterState)
{
Skipping = 0;
}
}
if(Skipping)
{
testMarkers[j].classList.add("skip");
}
}
}
}
else
{
filterItem.classList.remove("off");
if(!filterItem.parentNode.classList.contains("filter_media"))
{
filterItem.querySelector(".icon").style.backgroundColor = getComputedStyle(filterItem.querySelector(".icon")).getPropertyValue("border-color");
}
setDotLightness(filterItem.querySelector(".icon"));
var testMarkers = document.querySelectorAll(".marker.off_" + selectedCategory);
for(var j = 0; j < testMarkers.length; ++j)
{
if(filterState[selectedCategory].type == "topic")
{
testMarkers[j].classList.remove("off_" + selectedCategory);
testMarkers[j].classList.add("cat_" + selectedCategory);
var markerCategories = testMarkers[j].querySelectorAll(".category." + selectedCategory);
for(var k = 0; k < markerCategories.length; ++k)
{
if(markerCategories[k].classList.contains(selectedCategory))
{
markerCategories[k].classList.remove("off");
markerCategories[k].style.backgroundColor = getComputedStyle(markerCategories[k]).getPropertyValue("border-color");
setDotLightness(markerCategories[k]);
}
}
}
else
{
testMarkers[j].classList.remove("off_" + selectedCategory);
testMarkers[j].classList.add(selectedCategory);
}
Skipping = 0;
if(filterMode == "inclusive")
{
testMarkers[j].classList.remove("skip");
}
else
{
var markerClasses = testMarkers[j].classList;
for(var k = 0; k < markerClasses.length; ++k)
{
if(markerClasses[k].startsWith("off_"))
{
Skipping = 1;
}
}
if(!Skipping)
{
testMarkers[j].classList.remove("skip");
}
}
}
}
}
function resetFilter() {
for(i in filterItems)
{
if(filterItems[i].classList && filterItems[i].classList.contains("off"))
{
filterItemToggle(filterItems[i]);
}
}
if(filterMode == "exclusive")
{
toggleFilterMode();
}
}
function invertFilter(focusedElement) {
var siblings = focusedElement.parentNode.querySelectorAll(".filter_content");
for(i in siblings)
{
if(siblings[i].classList)
{
filterItemToggle(siblings[i]);
}
}
}
function resetFade() {
filter.classList.remove("responsible");
filter.querySelector(".filter_mode").classList.remove("responsible");
var responsibleCategories = filter.querySelectorAll(".filter_content.responsible");
for(var i = 0; i < responsibleCategories.length; ++i)
{
responsibleCategories[i].classList.remove("responsible");
}
}
function onRefChanged(ref, element) {
if(element.classList.contains("skip"))
{
if(!filter.classList.contains("responsible"))
{
filter.classList.add("responsible");
}
for(var selector = 0; selector < element.classList.length; ++selector)
{
if(element.classList[selector].startsWith("off_"))
{
if(!filter.querySelector(".filter_content." + element.classList[selector].replace(/^off_/, "")).classList.contains("responsible"))
{
filter.querySelector(".filter_content." + element.classList[selector].replace(/^off_/, "")).classList.add("responsible");
}
}
if((element.classList[selector].startsWith("cat_") || element.classList[selector] in filterState))
{
if(!filter.querySelector(".filter_mode").classList.add("responsible"))
{
filter.querySelector(".filter_mode").classList.add("responsible");
}
}
setTimeout(resetFade, 8000);
}
player.jumpToNextMarker();
return;
}
for (var MenuIndex = 0; MenuIndex < sourceMenus.length; ++MenuIndex)
{
var SetMenu = 0;
if (ref !== undefined && ref !== null) {
var refElements = sourceMenus[MenuIndex].querySelectorAll(".refs .ref");
var refs = ref.split(",");
for (var i = 0; i < refElements.length; ++i) {
if (refs.includes(refElements[i].getAttribute("data-id"))) {
refElements[i].classList.add("current");
SetMenu = 1;
} else {
refElements[i].classList.remove("current");
}
}
if(SetMenu) {
sourceMenus[MenuIndex].classList.add("current");
} else {
sourceMenus[MenuIndex].classList.remove("current");
}
} else {
sourceMenus[MenuIndex].classList.remove("current");
var refs = sourceMenus[MenuIndex].querySelectorAll(".refs .ref");
for (var i = 0; i < refs.length; ++i) {
refs[i].classList.remove("current");
}
}
}
}
function navigateFilter(filterItem) {
if(filterItem != lastFocusedCategory)
{
lastFocusedCategory.classList.remove("focused");
if(filterItem.parentNode.classList.contains("filter_topics"))
{
lastFocusedTopic = filterItem;
lastFocusedCategory = lastFocusedTopic;
}
else
{
lastFocusedMedium = filterItem;
lastFocusedCategory = lastFocusedMedium;
}
focusedElement = lastFocusedCategory;
focusedElement.classList.add("focused");
}
}
function mouseOverQuotes(quote) {
if(focusedElement && quote != lastFocusedQuote)
{
focusedElement.classList.remove("focused");
lastFocusedQuote = quote;
focusedElement = lastFocusedQuote;
focusedElement.classList.add("focused");
}
}
function mouseOverReferences(reference) {
if(focusedElement && reference != lastFocusedReference)
{
focusedElement.classList.remove("focused")
lastFocusedReference = reference;
}
focusedElement = lastFocusedReference;
focusedElement.classList.add("focused");
var ourIdentifiers = reference.querySelector(".ref_indices").children;
weWereLastFocused = false;
for(var k = 0; k < ourIdentifiers.length; ++k)
{
if(ourIdentifiers[k] == lastFocusedIdentifier)
{
weWereLastFocused = true;
}
}
if(!weWereLastFocused)
{
lastFocusedIdentifier.classList.remove("focused");
lastFocusedIdentifier = reference.querySelector(".ref_indices").firstElementChild;
}
focusedIdentifer = lastFocusedIdentifier;
focusedIdentifer.classList.add("focused");
for(var l = 0; l < ourIdentifiers.length; ++l)
{
ourIdentifiers[l].addEventListener("mouseenter", function(ev) {
if(this != lastFocusedIdentifier)
{
lastFocusedIdentifier.classList.remove("focused");
lastFocusedIdentifier = this;
lastFocusedIdentifier.classList.add("focused");
}
})
}
}
function mouseSkipToTimecode(player, time, ev)
{
player.setTime(parseInt(time, 10));
player.play();
ev.preventDefault();
ev.stopPropagation();
return false;
}
function handleMouseOverMenu(menu, eventType)
{
if(!(menu.classList.contains("visible")) && eventType == "mouseenter" ||
menu.classList.contains("visible") && eventType == "mouseleave")
{
if(menu.classList.contains("quotes"))
{
toggleMenuVisibility(quotesMenu);
}
else if(menu.classList.contains("references"))
{
toggleMenuVisibility(referencesMenu);
}
else if(menu.classList.contains("filter"))
{
toggleMenuVisibility(filterMenu);
}
else if(menu.classList.contains("credits"))
{
toggleMenuVisibility(creditsMenu);
}
}
if(eventType == "click" && menu.classList.contains("help"))
{
helpDocumentation.classList.toggle("visible");
}
}
function RGBtoHSL(colour)
{
var rgb = colour.slice(4, -1).split(", ");
var red = rgb[0];
var green = rgb[1];
var blue = rgb[2];
var min = Math.min(red, green, blue);
var max = Math.max(red, green, blue);
var chroma = max - min;
var hue = 0;
if(max == red)
{
hue = ((green - blue) / chroma) % 6;
}
else if(max == green)
{
hue = ((blue - red) / chroma) + 2;
}
else if(max == blue)
{
hue = ((red - green) / chroma) + 4;
}
var saturation = chroma / 255 * 100;
hue = (hue * 60) < 0 ? 360 + (hue * 60) : (hue * 60);
return [hue, saturation]
}
function getBackgroundBrightness(element) {
var colour = getComputedStyle(element).getPropertyValue("background-color");
var depth = 0;
while((colour == "transparent" || colour == "rgba(0, 0, 0, 0)") && depth <= 4)
{
element = element.parentNode;
colour = getComputedStyle(element).getPropertyValue("background-color");
++depth;
}
var rgb = colour.slice(4, -1).split(", ");
var result = Math.sqrt(rgb[0] * rgb[0] * .241 +
rgb[1] * rgb[1] * .691 +
rgb[2] * rgb[2] * .068);
return result;
}
function setTextLightness(textElement)
{
var textHue = textElement.getAttribute("data-hue");
var textSaturation = textElement.getAttribute("data-saturation");
if(getBackgroundBrightness(textElement.parentNode) < 127)
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 76%)");
}
else
{
textElement.style.color = ("hsl(" + textHue + ", " + textSaturation + ", 24%)");
}
}
function setDotLightness(topicDot)
{
var Hue = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[0];
var Saturation = RGBtoHSL(getComputedStyle(topicDot).getPropertyValue("background-color"))[1];
if(getBackgroundBrightness(topicDot.parentNode) < 127)
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 76%)");
}
else
{
topicDot.style.backgroundColor = ("hsl(" + Hue + ", " + Saturation + "%, 24%)");
topicDot.style.borderColor = ("hsl(" + Hue + ", " + Saturation + "%, 24%)");
}
}