2017-03-10 14:19:25 +00:00
// 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 ;
2017-03-11 02:48:11 +00:00
this . buffering = false ;
this . pauseAfterBuffer = false ;
2017-03-10 14:19:25 +00:00
this . speed = 1 ;
this . currentTime = 0 ;
this . lastFrameTime = 0 ;
this . scrollTo = - 1 ;
this . scrollPosition = 0 ;
this . nextFrame = null ;
this . looping = false ;
2017-06-03 01:32:18 +00:00
2017-03-10 14:19:25 +00:00
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 ( ) ;
}
2017-03-15 02:10:11 +00:00
this . pauseAfterBuffer = false ;
2017-03-10 14:19:25 +00:00
} 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 ( ) ;
2017-03-11 02:48:11 +00:00
} else if ( this . buffering ) {
this . pauseAfterBuffer = true ;
2017-03-10 14:19:25 +00:00
}
} 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 ( ) ;
} ;
2017-06-18 21:42:11 +00:00
function getElementXOffsetFromPage ( el ) {
var left = 0 ;
do {
left += el . offsetLeft ;
} while ( el = el . offsetParent ) ;
return left ;
}
2017-03-10 14:19:25 +00:00
Player . prototype . onMarkerMouseMove = function ( marker , ev ) {
if ( this . currentMarker == marker ) {
2017-06-18 21:42:11 +00:00
marker . hoverx = ( ev . pageX - getElementXOffsetFromPage ( marker . el ) ) / marker . el . offsetWidth ;
2017-03-10 14:19:25 +00:00
}
} ;
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 != p revMarker ) {
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 ;
}
2017-05-19 22:29:35 +00:00
if ( this . currentMarker ) {
this . refsCallback ( this . currentMarker . ref , this . currentMarker . el ) ;
2017-03-10 14:19:25 +00:00
} 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 ;
}
2017-03-11 02:48:11 +00:00
this . buffering = ev . data == YT . PlayerState . BUFFERING ;
if ( this . playing && this . pauseAfterBuffer ) {
this . pauseAfterBuffering = false ;
this . pause ( ) ;
}
2017-03-10 14:19:25 +00:00
} ;
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 ,
2017-06-03 01:32:18 +00:00
//playerVars: { disablekb: 1 },
2017-03-10 14:19:25 +00:00
events : {
"onReady" : this . onYoutubePlayerReady . bind ( this ) ,
"onStateChange" : this . onYoutubePlayerStateChange . bind ( this ) ,
"onPlaybackRateChange" : this . onYoutubePlayerPlaybackRateChange . bind ( this )
}
} ) ;
} ;
Player . youtubePlayerCount = 0 ;
2017-05-31 00:21:21 +00:00
// 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" ) ;
2017-06-11 22:49:04 +00:00
if ( focusedElement . parentNode . classList . contains ( "filter_topics" ) )
{
lastFocusedTopic = focusedElement . nextElementSibling ;
lastFocusedCategory = lastFocusedTopic ;
}
else
{
lastFocusedMedium = focusedElement . nextElementSibling ;
lastFocusedCategory = lastFocusedMedium ;
}
lastFocusedElement = lastFocusedCategory ;
focusedElement = lastFocusedElement ;
2017-05-31 00:21:21 +00:00
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" ) ;
2017-06-11 22:49:04 +00:00
if ( focusedElement . parentNode . classList . contains ( "filter_topics" ) )
{
lastFocusedTopic = focusedElement . previousElementSibling ;
lastFocusedCategory = lastFocusedTopic ;
}
else
{
lastFocusedMedium = focusedElement . previousElementSibling ;
lastFocusedCategory = lastFocusedMedium ;
}
lastFocusedElement = lastFocusedCategory ;
focusedElement = lastFocusedElement ;
2017-05-31 00:21:21 +00:00
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" ) ;
2017-06-03 01:32:18 +00:00
} break ;
2017-05-31 00:21:21 +00:00
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" ) ;
2017-06-11 22:49:04 +00:00
if ( ! filterItem . parentNode . classList . contains ( "filter_media" ) )
{
filterItem . querySelector ( ".icon" ) . style . backgroundColor = "transparent" ;
}
2017-05-31 00:21:21 +00:00
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" ) ;
2017-06-10 15:56:04 +00:00
markerCategories [ k ] . style . backgroundColor = "transparent" ;
2017-05-31 00:21:21 +00:00
}
}
}
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" ) ;
2017-06-11 22:49:04 +00:00
if ( ! filterItem . parentNode . classList . contains ( "filter_media" ) )
{
filterItem . querySelector ( ".icon" ) . style . backgroundColor = getComputedStyle ( filterItem . querySelector ( ".icon" ) ) . getPropertyValue ( "border-color" ) ;
}
2017-06-10 15:56:04 +00:00
setDotLightness ( filterItem . querySelector ( ".icon" ) ) ;
2017-05-31 00:21:21 +00:00
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" ) ;
2017-06-10 15:56:04 +00:00
markerCategories [ k ] . style . backgroundColor = getComputedStyle ( markerCategories [ k ] ) . getPropertyValue ( "border-color" ) ;
setDotLightness ( markerCategories [ k ] ) ;
2017-05-31 00:21:21 +00:00
}
}
}
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 )
{
2017-06-03 01:32:18 +00:00
if ( ! ( menu . classList . contains ( "visible" ) ) && eventType == "mouseenter" ||
menu . classList . contains ( "visible" ) && eventType == "mouseleave" )
2017-05-31 00:21:21 +00:00
{
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 ) ;
}
2017-06-03 01:32:18 +00:00
}
if ( eventType == "click" && menu . classList . contains ( "help" ) )
{
helpDocumentation . classList . toggle ( "visible" ) ;
2017-05-31 00:21:21 +00:00
}
}
2017-06-09 22:04:07 +00:00
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 ;
2017-06-10 15:56:04 +00:00
while ( ( colour == "transparent" || colour == "rgba(0, 0, 0, 0)" ) && depth <= 4 )
2017-06-09 22:04:07 +00:00
{
element = element . parentNode ;
colour = getComputedStyle ( element ) . getPropertyValue ( "background-color" ) ;
++ depth ;
}
var rgb = colour . slice ( 4 , - 1 ) . split ( ", " ) ;
2017-06-10 15:56:04 +00:00
var result = Math . sqrt ( rgb [ 0 ] * rgb [ 0 ] * . 241 +
2017-06-09 22:04:07 +00:00
rgb [ 1 ] * rgb [ 1 ] * . 691 +
rgb [ 2 ] * rgb [ 2 ] * . 068 ) ;
2017-06-10 15:56:04 +00:00
return result ;
2017-06-09 22:04:07 +00:00
}
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%)" ) ;
}
}