hmml_to_html.c: Generate keyboard navigation [#24]

With thanks to @insofaras for the onblur functionality
This commit is contained in:
Matt Mascarenhas 2017-06-03 02:32:18 +01:00
parent 00f6dbed51
commit 4ad0a0e737
3 changed files with 417 additions and 288 deletions

View File

@ -64,10 +64,10 @@ typedef struct
// TODO(matt): Parse this stuff out of a config file // TODO(matt): Parse this stuff out of a config file
char *Credentials[ ][5] = char *Credentials[ ][5] =
{ {
{ "Miblo", "Matt Mascarenhas", "http://miblodelcarpio.co.uk", "patreon_logo.png", "http://patreon.com/miblo"}, { "Miblo", "Matt Mascarenhas", "http://miblodelcarpio.co.uk", "patreon_logo.png", "https://patreon.com/miblo"},
{ "miotatsu", "Mio Iwakura", "http://riscy.tv/", "patreon_logo.png", "http://patreon.com/miotatsu"}, { "miotatsu", "Mio Iwakura", "http://riscy.tv/", "patreon_logo.png", "https://patreon.com/miotatsu"},
{ "nothings", "Sean Barrett", "https://nothings.org/", "", ""}, { "nothings", "Sean Barrett", "https://nothings.org/", "", ""},
{ "cmuratori", "Casey Muratori", "https://handmadehero.org", "patreon_logo.png", "http://patreon.com/cmuratori"}, { "cmuratori", "Casey Muratori", "https://handmadehero.org", "patreon_logo.png", "https://patreon.com/cmuratori"},
{ "fierydrake", "Mike Tunnicliffe", "", "", ""}, { "fierydrake", "Mike Tunnicliffe", "", "", ""},
}; };
@ -166,7 +166,7 @@ CopyStringToBuffer(buffer *Dest, char *Format, ...)
{ {
va_list Args; va_list Args;
va_start(Args, Format); va_start(Args, Format);
int Length = vsprintf(Dest->Ptr, Format, Args); int Length = vsnprintf(Dest->Ptr, Dest->Size - (Dest->Ptr - Dest->Location), Format, Args);
va_end(Args); va_end(Args);
// TODO(matt): // TODO(matt):
{ {
@ -310,7 +310,7 @@ BuildCredits(buffer *CreditsMenu, buffer *HostInfo, buffer *AnnotatorInfo, bool
if(*Credentials[CredentialIndex][2]) if(*Credentials[CredentialIndex][2])
{ {
CopyStringToBuffer(HostInfo, CopyStringToBuffer(HostInfo,
" <a href=\"%s\" target=\"_blank\" class=\"person\">\n" " <a class=\"person\" href=\"%s\" target=\"_blank\">\n"
" <div class=\"role\">Host</div>\n" " <div class=\"role\">Host</div>\n"
" <div class=\"name\">%s</div>\n" " <div class=\"name\">%s</div>\n"
" </a>\n", " </a>\n",
@ -330,7 +330,7 @@ Credentials[CredentialIndex][1]);
if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3]) if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3])
{ {
CopyStringToBuffer(HostInfo, CopyStringToBuffer(HostInfo,
" <a class=\"support\" href=\"%s\"><img src=\"%s\"></a>\n", " <a class=\"support\" href=\"%s\" target=\"_blank\"><img src=\"%s\"></a>\n",
Credentials[CredentialIndex][4], Credentials[CredentialIndex][4],
Credentials[CredentialIndex][3]); Credentials[CredentialIndex][3]);
} }
@ -347,7 +347,7 @@ Credentials[CredentialIndex][3]);
if(*Credentials[CredentialIndex][2]) if(*Credentials[CredentialIndex][2])
{ {
CopyStringToBuffer(AnnotatorInfo, CopyStringToBuffer(AnnotatorInfo,
" <a href=\"%s\" target=\"_blank\" class=\"person\">\n" " <a class=\"person\" href=\"%s\" target=\"_blank\">\n"
" <div class=\"role\">Annotator</div>\n" " <div class=\"role\">Annotator</div>\n"
" <div class=\"name\">%s</div>\n" " <div class=\"name\">%s</div>\n"
" </a>\n", " </a>\n",
@ -367,7 +367,7 @@ Credentials[CredentialIndex][1]);
if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3]) if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3])
{ {
CopyStringToBuffer(AnnotatorInfo, CopyStringToBuffer(AnnotatorInfo,
" <a class=\"support\" href=\"%s\"><img src=\"%s\"></a>\n", " <a class=\"support\" href=\"%s\" target=\"_blank\"><img src=\"%s\"></a>\n",
Credentials[CredentialIndex][4], Credentials[CredentialIndex][4],
Credentials[CredentialIndex][3]); Credentials[CredentialIndex][3]);
} }
@ -380,7 +380,7 @@ Credentials[CredentialIndex][3]);
if(FoundHost || FoundAnnotator) if(FoundHost || FoundAnnotator)
{ {
CopyStringToBuffer(CreditsMenu, CopyStringToBuffer(CreditsMenu,
" <div class=\"menu\">\n" " <div class=\"menu credits\">\n"
" <div class=\"mouse_catcher\"></div>\n" " <div class=\"mouse_catcher\"></div>\n"
" <span>Credits</span>\n" " <span>Credits</span>\n"
" <div class=\"credits_container\">\n"); " <div class=\"credits_container\">\n");
@ -443,7 +443,7 @@ BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMM
CopyString(ReferencesArray[UniqueRefs].Source, Ref.author); CopyString(ReferencesArray[UniqueRefs].Source, Ref.author);
CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title); CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title);
//TODO(matt): Look into finding the best ISBN searcher the web has to offer //TODO(matt): Look into finding the best ISBN searcher the web has to offer
CopyString(ReferencesArray[UniqueRefs].URL, "http://www.isbnsearch.org/isbn/%s", Ref.isbn); CopyString(ReferencesArray[UniqueRefs].URL, "http://www.openisbn.com/isbn/%s", Ref.isbn);
} }
else if(Ref.url && Ref.article && Ref.author) else if(Ref.url && Ref.article && Ref.author)
{ {
@ -1075,6 +1075,7 @@ main(int ArgC, char **Args)
// AnnotationData // AnnotationData
// Text // Text
// Category // Category
// Script
// FilterState // FilterState
buffer Master; buffer Master;
@ -1098,6 +1099,7 @@ main(int ArgC, char **Args)
buffer Text; buffer Text;
buffer Category; buffer Category;
buffer Script;
buffer FilterState; buffer FilterState;
for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex) for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex)
@ -1131,6 +1133,7 @@ main(int ArgC, char **Args)
// Player // Player
// Colour // Colour
// Annotation // Annotation
// Script
// FilterState // FilterState
ClaimBuffer(MemoryArena, &ClaimedMemory, &Master, "Master", Kilobytes(512)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Master, "Master", Kilobytes(512));
@ -1149,6 +1152,7 @@ main(int ArgC, char **Args)
ClaimBuffer(MemoryArena, &ClaimedMemory, &Colour, "Colour", 32); ClaimBuffer(MemoryArena, &ClaimedMemory, &Colour, "Colour", 32);
ClaimBuffer(MemoryArena, &ClaimedMemory, &Annotation, "Annotation", Kilobytes(8)); ClaimBuffer(MemoryArena, &ClaimedMemory, &Annotation, "Annotation", Kilobytes(8));
ClaimBuffer(MemoryArena, &ClaimedMemory, &Script, "Script", Kilobytes(8));
ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterState, "FilterState", Kilobytes(4)); ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterState, "FilterState", Kilobytes(4));
ref_info ReferencesArray[200] = { 0 }; ref_info ReferencesArray[200] = { 0 };
@ -1168,7 +1172,8 @@ main(int ArgC, char **Args)
CopyStringToBuffer(&Title, CopyStringToBuffer(&Title,
" <div class=\"title %s\">\n" " <div class=\"title %s\">\n"
" <span class=\"episode_name\">%s</span>\n", HMML.metadata.project, HMML.metadata.title); " <span class=\"episode_name\">%s</span>\n"
" <span id=\"focus-warn\">⚠ Click here to regain focus ⚠</span>\n", HMML.metadata.project, HMML.metadata.title);
CopyStringToBuffer(&Player, CopyStringToBuffer(&Player,
" <div class=\"player_container\">\n" " <div class=\"player_container\">\n"
@ -1283,10 +1288,10 @@ Readable);
if(!HasReferenceMenu) if(!HasReferenceMenu)
{ {
CopyStringToBuffer(&ReferenceMenu, CopyStringToBuffer(&ReferenceMenu,
" <div class=\"menu\">\n" " <div class=\"menu references\">\n"
" <span>References &#9660;</span>\n" " <span>References &#9660;</span>\n"
" <div class=\"mouse_catcher\"></div>\n" " <div class=\"mouse_catcher\"></div>\n"
" <div class=\"refs\">\n"); " <div class=\"refs references_container\">\n");
if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1) if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1)
{ {
@ -1407,10 +1412,10 @@ AppendedIdentifier:
if(!HasQuoteMenu) if(!HasQuoteMenu)
{ {
CopyStringToBuffer(&QuoteMenu, CopyStringToBuffer(&QuoteMenu,
" <div class=\"menu\">\n" " <div class=\"menu quotes\">\n"
" <span>Quotes &#9660;</span>\n" " <span>Quotes &#9660;</span>\n"
" <div class=\"mouse_catcher\"></div>\n" " <div class=\"mouse_catcher\"></div>\n"
" <div class=\"refs\">\n"); " <div class=\"refs quotes_container\">\n");
HasQuoteMenu = TRUE; HasQuoteMenu = TRUE;
} }
@ -1722,6 +1727,223 @@ CategoryMedium[j][2]
ParseConfig(&Config, HMML.metadata.annotator); ParseConfig(&Config, HMML.metadata.annotator);
#endif #endif
CopyStringToBuffer(&Title, CopyStringToBuffer(&Title,
" <div class=\"help\">\n"
" <span>?</span>\n"
" <div class=\"help_container\">\n"
" <span class=\"help_key\">?</span><h1>Keyboard Navigation</h1>\n"
"\n"
" <h2>Global Keys</h2>\n"
" <span class=\"help_key\">W</span>, <span class=\"help_key\">A</span>, <span class=\"help_key\">P</span> / <span class=\"help_key\">S</span>, <span class=\"help_key\">D</span>, <span class=\"help_key\">N</span> <span class=\"help_text\">Jump to previous / next marker</span><br>\n");
if(HasFilterMenu)
{
CopyStringToBuffer(&Title,
" <span class=\"help_key\">V</span> <span class=\"help_text\">Reset filter</span> <span class=\"help_key\">z</span> <span class=\"help_text\">Toggle filter mode between \"inclusive\" and \"exclusive\"</span>\n");
}
else
{
CopyStringToBuffer(&Title,
" <span class=\"help_key unavailable\">V</span> <span class=\"help_text unavailable\">Reset filter</span> <span class=\"help_key unavailable\">z</span> <span class=\"help_text unavailable\">Toggle filter mode between \"inclusive\" and \"exclusive\"</span>\n");
}
CopyStringToBuffer(&Title,
"\n"
" <h2>Menu toggling</h2>\n");
if(HasQuoteMenu)
{
CopyStringToBuffer(&Title,
" <span class=\"help_key\">q</span> <span class=\"help_text\">Quotes</span>\n");
}
else
{
CopyStringToBuffer(&Title,
" <span class=\"help_key unavailable\">q</span> <span class=\"help_text unavailable\">Quotes</span>\n");
}
if(HasReferenceMenu)
{
CopyStringToBuffer(&Title,
" <span class=\"help_key\">r</span> <span class=\"help_text\">References</span>\n");
}
else
{
CopyStringToBuffer(&Title,
" <span class=\"help_key unavailable\">r</span> <span class=\"help_text unavailable\">References</span>\n");
}
if(HasFilterMenu)
{
CopyStringToBuffer(&Title,
" <span class=\"help_key\">f</span> <span class=\"help_text\">Filter</span>\n");
}
else
{
CopyStringToBuffer(&Title,
" <span class=\"help_key unavailable\">f</span> <span class=\"help_text unavailable\">Filter</span>\n");
}
if(HasCreditsMenu)
{
CopyStringToBuffer(&Title,
" <span class=\"help_key\">c</span> <span class=\"help_text\">Credits</span>\n");
}
else
{
CopyStringToBuffer(&Title,
" <span class=\"help_key unavailable\">c</span> <span class=\"help_text unavailable\">Credits</span>\n");
}
CopyStringToBuffer(&Title,
"\n"
" <h2>Movement</h2>\n"
" <div class=\"help_paragraph\">\n"
" <div class=\"key_block\">\n"
" <div class=\"key_column\" style=\"flex-grow: 1\">\n"
" <span class=\"help_key\">a</span>\n"
" </div>\n"
" <div class=\"key_column\" style=\"flex-grow: 2\">\n"
" <span class=\"help_key\">w</span><br>\n"
" <span class=\"help_key\">s</span>\n"
" </div>\n"
" <div class=\"key_column\" style=\"flex-grow: 1\">\n"
" <span class=\"help_key\">d</span>\n"
" </div>\n"
" </div>\n"
" <div class=\"key_block\">\n"
" <span class=\"help_key\">h</span>\n"
" <span class=\"help_key\">j</span>\n"
" <span class=\"help_key\">k</span>\n"
" <span class=\"help_key\">l</span>\n"
" </div>\n"
" <div class=\"key_block\">\n"
" <div style=\"flex-grow: 1\">\n"
" <span class=\"help_key\">←</span>\n"
" </div>\n"
" <div style=\"flex-grow: 2\">\n"
" <span class=\"help_key\">↑</span><br>\n"
" <span class=\"help_key\">↓</span>\n"
" </div>\n"
" <div style=\"flex-grow: 1\">\n"
" <span class=\"help_key\">→</span>\n"
" </div>\n"
" </div>\n"
" </div>\n"
" <br>\n");
if(HasQuoteMenu)
{
CopyStringToBuffer(&Title,
" <h2>Quotes ");
if(HasReferenceMenu)
{
CopyStringToBuffer(&Title, "and References Menus</h2>\n");
}
else
{
CopyStringToBuffer(&Title, "<span class=\"unavailable\">and References</span> Menus</h2>\n");
}
}
else
{
CopyStringToBuffer(&Title,
" <h2><span class=\"unavailable\">Quotes");
if(HasReferenceMenu)
{
CopyStringToBuffer(&Title, " and</span> References Menus</h2>\n");
}
else
{
CopyStringToBuffer(&Title, " and References Menus</span></h2>\n");
}
}
if(HasQuoteMenu || HasReferenceMenu)
{
CopyStringToBuffer(&Title,
" <span style=\"width: auto\" class=\"help_key\">Enter</span> <span class=\"help_text\">Jump to timecode</span><br>\n");
}
else
{
CopyStringToBuffer(&Title,
" <span style=\"width: auto\" class=\"help_key unavailable\">Enter</span> <span class=\"help_text unavailable\">Jump to timecode</span><br>\n");
}
if(HasReferenceMenu)
{
CopyStringToBuffer(&Title,
" <h2>References ");
if(HasCreditsMenu)
{
CopyStringToBuffer(&Title, "and Credits Menus</h2>\n");
}
else
{
CopyStringToBuffer(&Title, "<span class=\"unavailable\">and Credits</span> Menus</h2>\n");
}
}
else
{
CopyStringToBuffer(&Title,
" <h2><span class=\"unavailable\">References");
if(HasCreditsMenu)
{
CopyStringToBuffer(&Title, " and</span> Credits Menus</h2>\n");
}
else
{
CopyStringToBuffer(&Title, " and Credits Menus</span></h2>\n");
}
}
if(HasReferenceMenu || HasCreditsMenu)
{
CopyStringToBuffer(&Title,
" <span class=\"help_key\">o</span> <span class=\"help_text\">Open URL (in new tab)</span>\n");
}
else
{
CopyStringToBuffer(&Title,
" <span class=\"help_key unavailable\">o</span> <span class=\"help_text unavailable\">Open URL (in new tab)</span>\n");
}
CopyStringToBuffer(&Title,
"\n");
if(HasFilterMenu)
{
CopyStringToBuffer(&Title,
" <h2>Filter Menu</h2>\n"
" <span class=\"help_key\">x</span>, <span style=\"width: auto\" class=\"help_key\">Space</span> <span class=\"help_text\">Toggle category and focus next</span><br>\n"
" <span class=\"help_key\">X</span>, <span style=\"width: auto; margin-right: 0px\" class=\"help_key\">Shift</span><span style=\"width: auto\" class=\"help_key\">Space</span> <span class=\"help_text\">Toggle category and focus previous</span><br>\n"
" <span class=\"help_key\">v</span> <span class=\"help_text\">Invert topics / media as per focus</span>\n");
}
else
{
CopyStringToBuffer(&Title,
" <h2><span class=\"unavailable\">Filter Menu</span></h2>\n"
" <span class=\"help_key unavailable\">x</span>, <span style=\"width: auto\" class=\"help_key unavailable\">Space</span> <span class=\"help_text unavailable\">Toggle category and focus next</span><br>\n"
" <span class=\"help_key unavailable\">X</span>, <span style=\"width: auto; margin-right: 0px\" class=\"help_key unavailable\">Shift</span><span style=\"width: auto\" class=\"help_key unavailable\">Space</span> <span class=\"help_text unavailable\">Toggle category and focus previous</span><br>\n"
" <span class=\"help_key unavailable\">v</span> <span class=\"help_text unavailable\">Invert topics / media as per focus</span>\n");
}
if(HasCreditsMenu)
{
CopyStringToBuffer(&Title,
" <h2>Credits Menu</h2>\n"
" <span style=\"width: auto\" class=\"help_key\">Enter</span> <span class=\"help_text\">Open URL (in new tab)</span><br>\n");
}
else
{
CopyStringToBuffer(&Title,
" <h2><span class=\"unavailable\">Credits Menu</span></h2>\n"
" <span style=\"width: auto\" class=\"help_key unavailable\">Enter</span> <span class=\"help_text unavailable\">Open URL (in new tab)</span><br>\n");
}
CopyStringToBuffer(&Title,
" </div>\n"
" </div>\n"
" </div>\n"); " </div>\n");
CopyStringToBuffer(&Player, CopyStringToBuffer(&Player,
@ -1749,56 +1971,176 @@ CategoryMedium[j][2]
HMML.metadata.title, HMML.metadata.title,
HMML.metadata.project); HMML.metadata.project);
//NOTE(matt): Here is where we do all our CopyBuffer() calls CopyStringToBuffer(&Script,
CopyBuffer(&Master, &Title);
CopyBuffer(&Master, &Player);
//
CopyStringToBuffer(&Master,
" <script>\n" " <script>\n"
" var player = new Player(document.querySelector(\".player_container\"), onRefChanged);\n"
" window.addEventListener(\"resize\", function() { player.updateSize(); });\n"
" document.addEventListener(\"keypress\", function(ev) {\n"
" switch (ev.key) {\n"
" case 'n':\n"
" case 'd':\n"
" case 's': {\n"
" player.jumpToNextMarker();\n"
" } break;\n"
"\n" "\n"
" case 'p':\n" "var menuState = [];\n");
" case 'a':\n"
" case 'w': {\n" if(HasQuoteMenu)
" player.jumpToPrevMarker();\n" {
" } break;\n" CopyStringToBuffer(&Script,
"var quotesMenu = document.querySelector(\".quotes_container\");\n"
"menuState.push(quotesMenu);\n"
"var quoteItems = quotesMenu.querySelectorAll(\".ref\");\n"
"for(var i = 0; i < quoteItems.length; ++i)\n"
"{\n"
" quoteItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
" mouseOverQuotes(this);\n"
" })\n"
"};\n"
"var lastFocusedQuote = null;\n");
}
if(HasReferenceMenu)
{
CopyStringToBuffer(&Script,
"var referencesMenu = document.querySelector(\".references_container\");\n"
"menuState.push(referencesMenu);\n"
"var referenceItems = referencesMenu.querySelectorAll(\".ref\");\n"
"for(var i = 0; i < referenceItems.length; ++i)\n"
"{\n"
" referenceItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
" mouseOverReferences(this);\n"
" })\n"
"};\n"
"var lastFocusedReference = null;\n"
"var lastFocusedIdentifier = null;\n");
}
if(HasFilterMenu)
{
CopyStringToBuffer(&Script,
"var filterMenu = document.querySelector(\".filter_container\");\n"
" menuState.push(filterMenu);\n"
" var lastFocusedCategory = null;\n"
" var lastFocusedTopic = null;\n"
" var lastFocusedMedium = null;\n"
"\n"
" var filter = filterMenu.parentNode;\n"
"\n"
" var filterModeElement = filter.querySelector(\".filter_mode\");\n"
" filterModeElement.addEventListener(\"click\", function(ev) {\n"
" toggleFilterMode();\n"
" });\n"
"\n"
" var filterMode = filterModeElement.classList[1];\n"
" var filterItems = filter.querySelectorAll(\".filter_content\");\n"
" for(var i = 0; i < filterItems.length; ++i)\n"
" {\n"
" filterItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
" navigateFilter(this);\n"
" })\n"
"\n"
" filterItems[i].addEventListener(\"click\", function(ev) {\n"
" filterItemToggle(this);\n"
" });\n"
"\n"
"%s\n"
"}\n", FilterState.Location);
}
if(HasCreditsMenu)
{
CopyStringToBuffer(&Script,
"var creditsMenu = document.querySelector(\".credits_container\");\n"
"if(creditsMenu)\n"
"{\n"
" menuState.push(creditsMenu);\n"
" var lastFocusedCreditItem = null;\n"
"\n"
" var creditItems = creditsMenu.querySelectorAll(\".person, .support\");\n"
" for(var i = 0; i < creditItems.length; ++i)\n"
" {\n"
" creditItems[i].addEventListener(\"mouseenter\", function(ev) {\n"
" if(this != lastFocusedCreditItem)\n"
" {\n"
" lastFocusedCreditItem.classList.remove(\"focused\");\n"
" lastFocusedCreditItem = this;\n"
" focusedElement = lastFocusedCreditItem;\n"
" focusedElement.classList.add(\"focused\");\n"
" }\n"
" })\n"
" }\n"
"}\n");
}
CopyStringToBuffer(&Script,
"var sourceMenus = document.querySelectorAll(\".menu\");\n"
"\n"
"var helpButton = document.querySelector(\".help\");\n"
"var helpDocumentation = helpButton.querySelector(\".help_container\");\n"
"helpButton.addEventListener(\"click\", function(ev) {\n"
" handleMouseOverMenu(this, ev.type);\n"
"})\n"
"\n"
"var focusedElement = null;\n"
"var focusedIdentifier = null;\n"
"\n"
"var player = new Player(document.querySelector(\".player_container\"), onRefChanged);\n"
"window.addEventListener(\"resize\", function() { player.updateSize(); });\n"
"document.addEventListener(\"keydown\", function(ev) {\n"
" var key = ev.key;\n"
" if(ev.getModifierState(\"Shift\") && key == \" \")\n"
" {\n"
" key = \"capitalSpace\";\n"
" }\n"
"\n"
" if(handleKey(key) == true && focusedElement)\n"
" {\n"
" ev.preventDefault();\n"
" }\n" " }\n"
"});\n" "});\n"
"\n" "\n"
"for(var i = 0; i < sourceMenus.length; ++i)\n"
"{\n"
" sourceMenus[i].addEventListener(\"mouseenter\", function(ev) {\n"
" handleMouseOverMenu(this, ev.type);\n"
" })\n"
" sourceMenus[i].addEventListener(\"mouseleave\", function(ev) {\n"
" handleMouseOverMenu(this, ev.type);\n"
" })\n"
"};\n"
"\n"
"var refTimecodes = document.querySelectorAll(\".refs .ref .timecode\");\n" "var refTimecodes = document.querySelectorAll(\".refs .ref .timecode\");\n"
"for (var i = 0; i < refTimecodes.length; ++i) {\n" "for (var i = 0; i < refTimecodes.length; ++i) {\n"
" refTimecodes[i].addEventListener(\"click\", function(ev) {\n" " refTimecodes[i].addEventListener(\"click\", function(ev) {\n"
" if (player) {\n" " if (player) {\n"
" var time = ev.currentTarget.getAttribute(\"data-timestamp\");\n" " var time = ev.currentTarget.getAttribute(\"data-timestamp\");\n"
" player.setTime(parseInt(time, 10));\n" " mouseSkipToTimecode(player, time, ev);\n"
" player.play();\n"
" ev.preventDefault();\n"
" ev.stopPropagation();\n"
" return false;\n"
" }\n" " }\n"
" });\n" " });\n"
"}\n" "}\n"
"\n" "\n"
"var filter = document.querySelector(\".filter\");\n" "var refSources = document.querySelectorAll(\".refs .ref\"); // This is for both quotes and refs\n"
"var filterModeElement = filter.querySelector(\".filter_mode\");\n" "for (var i = 0; i < refSources.length; ++i) {\n"
"var filterMode = filterModeElement.classList[1];\n"); " refSources[i].addEventListener(\"click\", function(ev) {\n"
" if (player) {\n"
" player.pause();\n"
" }\n"
" });\n"
"}\n"
"\n"
"var testMarkers = document.querySelectorAll(\".marker\");\n"
"\n"
"window.addEventListener(\"blur\", function(){\n"
" document.getElementById(\"focus-warn\").style.display = \"block\";\n"
"});\n"
"\n"
"window.addEventListener(\"focus\", function(){\n"
" document.getElementById(\"focus-warn\").style.display = \"none\";\n"
"});\n"
"\n"
" </script>\n");
if(HasFilterMenu) //NOTE(matt): Here is where we do all our CopyBuffer() calls
{ CopyBuffer(&Master, &Title);
CopyBuffer(&Master, &FilterState); CopyBuffer(&Master, &Player);
} CopyBuffer(&Master, &Script);
//
// NOTE(matt): Tree structure of "global" buffer dependencies // NOTE(matt): Tree structure of "global" buffer dependencies
// FilterState // FilterState
// Script
// Annotation // Annotation
// Colour // Colour
// Player // Player
@ -1813,6 +2155,7 @@ HMML.metadata.project);
// Title // Title
DeclaimBuffer(&FilterState, &ClaimedMemory); DeclaimBuffer(&FilterState, &ClaimedMemory);
DeclaimBuffer(&Script, &ClaimedMemory);
DeclaimBuffer(&Annotation, &ClaimedMemory); DeclaimBuffer(&Annotation, &ClaimedMemory);
DeclaimBuffer(&Colour, &ClaimedMemory); DeclaimBuffer(&Colour, &ClaimedMemory);
DeclaimBuffer(&Player, &ClaimedMemory); DeclaimBuffer(&Player, &ClaimedMemory);
@ -1828,235 +2171,6 @@ HMML.metadata.project);
DeclaimBuffer(&Title, &ClaimedMemory); DeclaimBuffer(&Title, &ClaimedMemory);
CopyStringToBuffer(&Master, CopyStringToBuffer(&Master,
"// Filter Mode Toggle\n"
"var testMarkers = document.querySelectorAll(\".marker\");\n"
"filterModeElement.addEventListener(\"click\", function(ev) {\n"
" if(filterMode == \"inclusive\")\n"
" {\n"
" filterModeElement.classList.remove(\"inclusive\");\n"
" filterModeElement.classList.add(\"exclusive\");\n"
" filterMode = \"exclusive\";\n"
"\n"
" for(var i = 0; i < testMarkers.length; ++i)\n"
" {\n"
" var testCategories = testMarkers[i].classList;\n"
" for(var j = 0; j < testCategories.length; ++j)\n"
" {\n"
" if((testCategories[j].startsWith(\"off_\")) && !testMarkers[i].classList.contains(\"skip\"))\n"
" {\n"
" testMarkers[i].classList.add(\"skip\");\n"
" }\n"
" }\n"
" }\n"
" }\n"
" else\n"
" {\n"
" filterModeElement.classList.remove(\"exclusive\");\n"
" filterModeElement.classList.add(\"inclusive\");\n"
" filterMode = \"inclusive\";\n"
"\n"
" for(var i = 0; i < testMarkers.length; ++i)\n"
" {\n"
" var testCategories = testMarkers[i].classList;\n"
" for(var j = 0; j < testCategories.length; ++j)\n"
" {\n"
" if((testCategories[j] in filterState || testCategories[j].startsWith(\"cat_\")) && testMarkers[i].classList.contains(\"skip\"))\n"
" {\n"
" testMarkers[i].classList.remove(\"skip\");\n"
" }\n"
" }\n"
" }\n"
" }\n"
"});\n"
"\n"
"// Filter Toggle\n"
"var filterCategories = filter.querySelectorAll(\".filter_topics .filter_content,.filter_media .filter_content\");\n"
"for(var i = 0; i < filterCategories.length; ++i)\n"
"{\n"
" filterCategories[i].addEventListener(\"click\", function(ev) {\n"
" var selectedCategory = this.classList[1];\n"
" filterState[selectedCategory].off = !filterState[selectedCategory].off;\n"
"\n"
" if(filterState[selectedCategory].off)\n"
" {\n"
" this.classList.add(\"off\");\n"
" var testMarkers = document.querySelectorAll(\".marker.\" + selectedCategory + \", .marker.cat_\" + selectedCategory);\n"
" for(var j = 0; j < testMarkers.length; ++j)\n"
" {\n"
" if(filterState[selectedCategory].type == \"topic\")\n"
" {\n"
" testMarkers[j].classList.remove(\"cat_\" + selectedCategory);\n"
" testMarkers[j].classList.add(\"off_\" + selectedCategory);\n"
" var markerCategories = testMarkers[j].querySelectorAll(\".category.\" + selectedCategory);\n"
" for(var k = 0; k < markerCategories.length; ++k)\n"
" {\n"
" if(markerCategories[k].classList.contains(selectedCategory))\n"
" {\n"
" markerCategories[k].classList.add(\"off\");\n"
" }\n"
" }\n"
" }\n"
" else\n"
" {\n"
" testMarkers[j].classList.remove(selectedCategory);\n"
" testMarkers[j].classList.add(\"off_\" + selectedCategory);\n"
" }\n"
"\n"
" Skipping = 1;\n"
" if(filterMode == \"exclusive\")\n"
" {\n"
" testMarkers[j].classList.add(\"skip\");\n"
" }\n"
" else\n"
" {\n"
" var markerClasses = testMarkers[j].classList;\n"
" for(var k = 0; k < markerClasses.length; ++k)\n"
" {\n"
" if(markerClasses[k] in filterState || markerClasses[k].replace(/^cat_/, \"\") in filterState)\n"
" {\n"
" Skipping = 0;\n"
" }\n"
" }\n"
" if(Skipping)\n"
" {\n"
" testMarkers[j].classList.add(\"skip\");\n"
" }\n"
" }\n"
"\n"
" }\n"
" }\n"
" else\n"
" {\n"
" this.classList.remove(\"off\");\n"
" var testMarkers = document.querySelectorAll(\".marker.off_\" + selectedCategory);\n"
" for(var j = 0; j < testMarkers.length; ++j)\n"
" {\n"
" if(filterState[selectedCategory].type == \"topic\")\n"
" {\n"
" testMarkers[j].classList.remove(\"off_\" + selectedCategory);\n"
" testMarkers[j].classList.add(\"cat_\" + selectedCategory);\n"
" var markerCategories = testMarkers[j].querySelectorAll(\".category.\" + selectedCategory);\n"
" for(var k = 0; k < markerCategories.length; ++k)\n"
" {\n"
" if(markerCategories[k].classList.contains(selectedCategory))\n"
" {\n"
" markerCategories[k].classList.remove(\"off\");\n"
" }\n"
" }\n"
" }\n"
" else\n"
" {\n"
" testMarkers[j].classList.remove(\"off_\" + selectedCategory);\n"
" testMarkers[j].classList.add(selectedCategory);\n"
" }\n"
"\n"
" Skipping = 0;\n"
" if(filterMode == \"inclusive\")\n"
" {\n"
" testMarkers[j].classList.remove(\"skip\");\n"
" }\n"
" else\n"
" {\n"
" var markerClasses = testMarkers[j].classList;\n"
" for(var k = 0; k < markerClasses.length; ++k)\n"
" {\n"
" if(markerClasses[k].startsWith(\"off_\"))\n"
" {\n"
" Skipping = 1;\n"
" }\n"
" }\n"
" if(!Skipping)\n"
" {\n"
" testMarkers[j].classList.remove(\"skip\");\n"
" }\n"
" }\n"
" }\n"
" }\n"
" });\n"
"}\n"
"\n"
"var refSources = document.querySelectorAll(\".refs .ref\");\n"
"for (var i = 0; i < refSources.length; ++i) {\n"
" refSources[i].addEventListener(\"click\", function(ev) {\n"
" if (player) {\n"
" player.pause();\n"
" }\n"
" });\n"
"}\n"
"\n"
"function resetFade()\n"
"{\n"
" filter.classList.remove(\"responsible\");\n"
" filter.querySelector(\".filter_mode\").classList.remove(\"responsible\");\n"
" var responsibleCategories = filter.querySelectorAll(\".filter_content.responsible\");\n"
" for(var i = 0; i < responsibleCategories.length; ++i)\n"
" {\n"
" responsibleCategories[i].classList.remove(\"responsible\");\n"
" }\n"
"}\n"
"\n"
"var sourceMenus = document.querySelectorAll(\".menu\");\n"
"function onRefChanged(ref, element) {\n"
" if(element.classList.contains(\"skip\"))\n"
" {\n"
" if(!filter.classList.contains(\"responsible\"))\n"
" {\n"
" filter.classList.add(\"responsible\");\n"
" }\n"
"\n"
" for(var selector = 0; selector < element.classList.length; ++selector)\n"
" {\n"
" if(element.classList[selector].startsWith(\"off_\"))\n"
" {\n"
" if(!filter.querySelector(\".filter_content.\" + element.classList[selector].replace(/^off_/, \"\")).classList.contains(\"responsible\"))\n"
" {\n"
" filter.querySelector(\".filter_content.\" + element.classList[selector].replace(/^off_/, \"\")).classList.add(\"responsible\");\n"
" }\n"
" }\n"
" if((element.classList[selector].startsWith(\"cat_\") || element.classList[selector] in filterState))\n"
" {\n"
" if(!filter.querySelector(\".filter_mode\").classList.add(\"responsible\"))\n"
" {\n"
" filter.querySelector(\".filter_mode\").classList.add(\"responsible\");\n"
" }\n"
" }\n"
" setTimeout(resetFade, 8000);\n"
" }\n"
" player.jumpToNextMarker();\n"
" return;\n"
" }\n"
"\n"
" for (var MenuIndex = 0; MenuIndex < sourceMenus.length; ++MenuIndex)\n"
" {\n"
" var SetMenu = 0;\n"
" if (ref !== undefined && ref !== null) {\n"
" var refElements = sourceMenus[MenuIndex].querySelectorAll(\".refs .ref\");\n"
" var refs = ref.split(\",\");\n"
"\n"
" for (var i = 0; i < refElements.length; ++i) {\n"
" if (refs.includes(refElements[i].getAttribute(\"data-id\"))) {\n"
" refElements[i].classList.add(\"current\");\n"
" SetMenu = 1;\n"
" } else {\n"
" refElements[i].classList.remove(\"current\");\n"
" }\n"
" }\n"
" if(SetMenu) {\n"
" sourceMenus[MenuIndex].classList.add(\"current\");\n"
" } else {\n"
" sourceMenus[MenuIndex].classList.remove(\"current\");\n"
" }\n"
"\n"
" } else {\n"
" sourceMenus[MenuIndex].classList.remove(\"current\");\n"
" var refs = sourceMenus[MenuIndex].querySelectorAll(\".refs .ref\");\n"
" for (var i = 0; i < refs.length; ++i) {\n"
" refs[i].classList.remove(\"current\");\n"
" }\n"
" }\n"
" }\n"
"}\n"
" </script>\n"
" </body>\n" " </body>\n"
"</html>\n"); "</html>\n");

View File

@ -49,6 +49,7 @@ function Player(htmlContainer, refsCallback) {
this.nextFrame = null; this.nextFrame = null;
this.looping = false; this.looping = false;
this.markersContainer.addEventListener("wheel", function(ev) { this.markersContainer.addEventListener("wheel", function(ev) {
this.scrollTo = -1; this.scrollTo = -1;
}.bind(this)); }.bind(this));
@ -302,6 +303,7 @@ Player.prototype.onYoutubeReady = function() {
videoId: this.videoContainer.getAttribute("data-videoId"), videoId: this.videoContainer.getAttribute("data-videoId"),
width: this.videoContainer.offsetWidth, width: this.videoContainer.offsetWidth,
height: this.videoContainer.offsetWidth / 16 * 9, height: this.videoContainer.offsetWidth / 16 * 9,
//playerVars: { disablekb: 1 },
events: { events: {
"onReady": this.onYoutubePlayerReady.bind(this), "onReady": this.onYoutubePlayerReady.bind(this),
"onStateChange": this.onYoutubePlayerStateChange.bind(this), "onStateChange": this.onYoutubePlayerStateChange.bind(this),
@ -735,7 +737,7 @@ function handleKey(key) {
case "?": { case "?": {
helpDocumentation.classList.toggle("visible"); helpDocumentation.classList.toggle("visible");
} } break;
case 'N': case 'N':
case 'D': case 'D':
@ -1078,8 +1080,8 @@ function mouseSkipToTimecode(player, time, ev)
function handleMouseOverMenu(menu, eventType) function handleMouseOverMenu(menu, eventType)
{ {
if(!menu.classList.contains("visible" && eventType == "mouseenter") || if(!(menu.classList.contains("visible")) && eventType == "mouseenter" ||
menu.classList.contains("visible" && eventType == "mouseleave")) menu.classList.contains("visible") && eventType == "mouseleave")
{ {
if(menu.classList.contains("quotes")) if(menu.classList.contains("quotes"))
{ {
@ -1097,9 +1099,9 @@ function handleMouseOverMenu(menu, eventType)
{ {
toggleMenuVisibility(creditsMenu); toggleMenuVisibility(creditsMenu);
} }
else if(menu.classList.contains("help")) }
if(eventType == "click" && menu.classList.contains("help"))
{ {
helpDocumentation.classList.toggle("visible"); helpDocumentation.classList.toggle("visible");
} }
}
} }

View File

@ -15,7 +15,13 @@
} }
.title .episode_name { .title .episode_name {
flex: 1 1; flex: 1;
}
.title > #focus-warn {
color: #F00;
flex: 1;
margin: auto;
} }
.title > .menu { .title > .menu {
@ -53,17 +59,24 @@
margin: 2px; margin: 2px;
} }
.help_key.unavailable,
.help_text.unavailable,
.help_container h2 .unavailable {
opacity: 0.32;
}
.key_block { .key_block {
display: inline-flex; display: inline-flex;
align-items: flex-end; align-items: flex-end;
flex-direction: row; flex-direction: row;
margin-right: 4px; margin: 8px;
} }
.help_text { .help_text {
margin: 0 8px 0 2px; margin: 0 8px 0 2px;
} }
.help_container h1 { .help_container h1 {
display: inline; display: inline;
margin-left: 4px; margin-left: 4px;