From 4ad0a0e73768b00dd02d27c4e8453e7e7d07dddf Mon Sep 17 00:00:00 2001 From: Matt Mascarenhas Date: Sat, 3 Jun 2017 02:32:18 +0100 Subject: [PATCH] hmml_to_html.c: Generate keyboard navigation [#24] With thanks to @insofaras for the onblur functionality --- hmml_to_html/hmml_to_html.c | 672 +++++++++++++++++++++--------------- hmml_to_html/player.js | 16 +- hmml_to_html/style.css | 17 +- 3 files changed, 417 insertions(+), 288 deletions(-) diff --git a/hmml_to_html/hmml_to_html.c b/hmml_to_html/hmml_to_html.c index eb0f54f..7bd5ce1 100644 --- a/hmml_to_html/hmml_to_html.c +++ b/hmml_to_html/hmml_to_html.c @@ -64,10 +64,10 @@ typedef struct // TODO(matt): Parse this stuff out of a config file char *Credentials[ ][5] = { - { "Miblo", "Matt Mascarenhas", "http://miblodelcarpio.co.uk", "patreon_logo.png", "http://patreon.com/miblo"}, - { "miotatsu", "Mio Iwakura", "http://riscy.tv/", "patreon_logo.png", "http://patreon.com/miotatsu"}, + { "Miblo", "Matt Mascarenhas", "http://miblodelcarpio.co.uk", "patreon_logo.png", "https://patreon.com/miblo"}, + { "miotatsu", "Mio Iwakura", "http://riscy.tv/", "patreon_logo.png", "https://patreon.com/miotatsu"}, { "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", "", "", ""}, }; @@ -166,7 +166,7 @@ CopyStringToBuffer(buffer *Dest, char *Format, ...) { va_list Args; 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); // TODO(matt): { @@ -310,7 +310,7 @@ BuildCredits(buffer *CreditsMenu, buffer *HostInfo, buffer *AnnotatorInfo, bool if(*Credentials[CredentialIndex][2]) { CopyStringToBuffer(HostInfo, -" \n" +" \n" "
Host
\n" "
%s
\n" "
\n", @@ -330,7 +330,7 @@ Credentials[CredentialIndex][1]); if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3]) { CopyStringToBuffer(HostInfo, -" \n", +" \n", Credentials[CredentialIndex][4], Credentials[CredentialIndex][3]); } @@ -347,7 +347,7 @@ Credentials[CredentialIndex][3]); if(*Credentials[CredentialIndex][2]) { CopyStringToBuffer(AnnotatorInfo, -" \n" +" \n" "
Annotator
\n" "
%s
\n" "
\n", @@ -367,7 +367,7 @@ Credentials[CredentialIndex][1]); if(*Credentials[CredentialIndex][4] && *Credentials[CredentialIndex][3]) { CopyStringToBuffer(AnnotatorInfo, -" \n", +" \n", Credentials[CredentialIndex][4], Credentials[CredentialIndex][3]); } @@ -380,7 +380,7 @@ Credentials[CredentialIndex][3]); if(FoundHost || FoundAnnotator) { CopyStringToBuffer(CreditsMenu, -"
\n" +"
\n" "
\n" " Credits\n" "
\n"); @@ -443,7 +443,7 @@ BuildReference(ref_info *ReferencesArray, int RefIdentifier, int UniqueRefs, HMM CopyString(ReferencesArray[UniqueRefs].Source, Ref.author); CopyString(ReferencesArray[UniqueRefs].RefTitle, Ref.title); //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) { @@ -1075,7 +1075,8 @@ main(int ArgC, char **Args) // AnnotationData // Text // Category - // FilterState + // Script + // FilterState buffer Master; @@ -1098,6 +1099,7 @@ main(int ArgC, char **Args) buffer Text; buffer Category; + buffer Script; buffer FilterState; for(int FileIndex = 1; FileIndex < ArgC; ++FileIndex) @@ -1131,7 +1133,8 @@ main(int ArgC, char **Args) // Player // Colour // Annotation - // FilterState + // Script + // FilterState 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, &Annotation, "Annotation", Kilobytes(8)); + ClaimBuffer(MemoryArena, &ClaimedMemory, &Script, "Script", Kilobytes(8)); ClaimBuffer(MemoryArena, &ClaimedMemory, &FilterState, "FilterState", Kilobytes(4)); ref_info ReferencesArray[200] = { 0 }; @@ -1168,7 +1172,8 @@ main(int ArgC, char **Args) CopyStringToBuffer(&Title, "
\n" -" %s\n", HMML.metadata.project, HMML.metadata.title); +" %s\n" +" ⚠ Click here to regain focus ⚠\n", HMML.metadata.project, HMML.metadata.title); CopyStringToBuffer(&Player, "
\n" @@ -1283,10 +1288,10 @@ Readable); if(!HasReferenceMenu) { CopyStringToBuffer(&ReferenceMenu, -"
\n" +"
\n" " References ▼\n" "
\n" -"
\n"); +"
\n"); if(BuildReference(ReferencesArray, RefIdentifier, UniqueRefs, *CurrentRef, *Anno) == 1) { @@ -1407,10 +1412,10 @@ AppendedIdentifier: if(!HasQuoteMenu) { CopyStringToBuffer(&QuoteMenu, -"
\n" +"
\n" " Quotes ▼\n" "
\n" -"
\n"); +"
\n"); HasQuoteMenu = TRUE; } @@ -1722,6 +1727,223 @@ CategoryMedium[j][2] ParseConfig(&Config, HMML.metadata.annotator); #endif CopyStringToBuffer(&Title, +"
\n" +" ?\n" +"
\n" +" ?

Keyboard Navigation

\n" +"\n" +"

Global Keys

\n" +" W, A, P / S, D, N Jump to previous / next marker
\n"); + + if(HasFilterMenu) + { + CopyStringToBuffer(&Title, +" V Reset filter z Toggle filter mode between \"inclusive\" and \"exclusive\"\n"); + } + else + { + CopyStringToBuffer(&Title, +" V Reset filter z Toggle filter mode between \"inclusive\" and \"exclusive\"\n"); + } + + CopyStringToBuffer(&Title, +"\n" +"

Menu toggling

\n"); + + if(HasQuoteMenu) + { + CopyStringToBuffer(&Title, +" q Quotes\n"); + } + else + { + CopyStringToBuffer(&Title, +" q Quotes\n"); + } + + if(HasReferenceMenu) + { + CopyStringToBuffer(&Title, +" r References\n"); + } + else + { + CopyStringToBuffer(&Title, +" r References\n"); + } + + if(HasFilterMenu) + { + CopyStringToBuffer(&Title, +" f Filter\n"); + } + else + { + CopyStringToBuffer(&Title, +" f Filter\n"); + } + + if(HasCreditsMenu) + { + CopyStringToBuffer(&Title, +" c Credits\n"); + } + else + { + CopyStringToBuffer(&Title, +" c Credits\n"); + } + + CopyStringToBuffer(&Title, +"\n" +"

Movement

\n" +"
\n" +"
\n" +"
\n" +" a\n" +"
\n" +"
\n" +" w
\n" +" s\n" +"
\n" +"
\n" +" d\n" +"
\n" +"
\n" +"
\n" +" h\n" +" j\n" +" k\n" +" l\n" +"
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +" \n" +"
\n" +"
\n" +"
\n" +"
\n"); + + if(HasQuoteMenu) + { + CopyStringToBuffer(&Title, +"

Quotes "); + if(HasReferenceMenu) + { + CopyStringToBuffer(&Title, "and References Menus

\n"); + } + else + { + CopyStringToBuffer(&Title, "and References Menus\n"); + } + } + else + { + CopyStringToBuffer(&Title, +"

Quotes"); + if(HasReferenceMenu) + { + CopyStringToBuffer(&Title, " and References Menus

\n"); + } + else + { + CopyStringToBuffer(&Title, " and References Menus\n"); + } + } + + if(HasQuoteMenu || HasReferenceMenu) + { + CopyStringToBuffer(&Title, +" Enter Jump to timecode
\n"); + } + else + { + CopyStringToBuffer(&Title, +" Enter Jump to timecode
\n"); + } + + if(HasReferenceMenu) + { + CopyStringToBuffer(&Title, +"

References "); + if(HasCreditsMenu) + { + CopyStringToBuffer(&Title, "and Credits Menus

\n"); + } + else + { + CopyStringToBuffer(&Title, "and Credits Menus\n"); + } + } + else + { + CopyStringToBuffer(&Title, +"

References"); + if(HasCreditsMenu) + { + CopyStringToBuffer(&Title, " and Credits Menus

\n"); + } + else + { + CopyStringToBuffer(&Title, " and Credits Menus\n"); + } + } + + if(HasReferenceMenu || HasCreditsMenu) + { + CopyStringToBuffer(&Title, +" o Open URL (in new tab)\n"); + } + else + { + CopyStringToBuffer(&Title, +" o Open URL (in new tab)\n"); + } + + CopyStringToBuffer(&Title, +"\n"); + + if(HasFilterMenu) + { + CopyStringToBuffer(&Title, +"

Filter Menu

\n" +" x, Space Toggle category and focus next
\n" +" X, ShiftSpace Toggle category and focus previous
\n" +" v Invert topics / media as per focus\n"); + } + else + { + CopyStringToBuffer(&Title, +"

Filter Menu

\n" +" x, Space Toggle category and focus next
\n" +" X, ShiftSpace Toggle category and focus previous
\n" +" v Invert topics / media as per focus\n"); + } + + if(HasCreditsMenu) + { + CopyStringToBuffer(&Title, +"

Credits Menu

\n" +" Enter Open URL (in new tab)
\n"); + } + else + { + CopyStringToBuffer(&Title, +"

Credits Menu

\n" +" Enter Open URL (in new tab)
\n"); + } + + CopyStringToBuffer(&Title, +"
\n" +"
\n" + "
\n"); CopyStringToBuffer(&Player, @@ -1749,56 +1971,176 @@ CategoryMedium[j][2] HMML.metadata.title, HMML.metadata.project); - //NOTE(matt): Here is where we do all our CopyBuffer() calls - CopyBuffer(&Master, &Title); - CopyBuffer(&Master, &Player); - // - - CopyStringToBuffer(&Master, + CopyStringToBuffer(&Script, " \n"); - if(HasFilterMenu) - { - CopyBuffer(&Master, &FilterState); - } + //NOTE(matt): Here is where we do all our CopyBuffer() calls + CopyBuffer(&Master, &Title); + CopyBuffer(&Master, &Player); + CopyBuffer(&Master, &Script); + // // NOTE(matt): Tree structure of "global" buffer dependencies - // FilterState + // FilterState + // Script // Annotation // Colour // Player @@ -1813,6 +2155,7 @@ HMML.metadata.project); // Title DeclaimBuffer(&FilterState, &ClaimedMemory); + DeclaimBuffer(&Script, &ClaimedMemory); DeclaimBuffer(&Annotation, &ClaimedMemory); DeclaimBuffer(&Colour, &ClaimedMemory); DeclaimBuffer(&Player, &ClaimedMemory); @@ -1828,235 +2171,6 @@ HMML.metadata.project); DeclaimBuffer(&Title, &ClaimedMemory); 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" -" \n" " \n" "\n"); diff --git a/hmml_to_html/player.js b/hmml_to_html/player.js index 8e4d65f..1a2df19 100644 --- a/hmml_to_html/player.js +++ b/hmml_to_html/player.js @@ -49,6 +49,7 @@ function Player(htmlContainer, refsCallback) { this.nextFrame = null; this.looping = false; + this.markersContainer.addEventListener("wheel", function(ev) { this.scrollTo = -1; }.bind(this)); @@ -302,6 +303,7 @@ Player.prototype.onYoutubeReady = function() { 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), @@ -735,7 +737,7 @@ function handleKey(key) { case "?": { helpDocumentation.classList.toggle("visible"); - } + } break; case 'N': case 'D': @@ -1078,8 +1080,8 @@ function mouseSkipToTimecode(player, time, ev) function handleMouseOverMenu(menu, eventType) { - if(!menu.classList.contains("visible" && eventType == "mouseenter") || - menu.classList.contains("visible" && eventType == "mouseleave")) + if(!(menu.classList.contains("visible")) && eventType == "mouseenter" || + menu.classList.contains("visible") && eventType == "mouseleave") { if(menu.classList.contains("quotes")) { @@ -1097,9 +1099,9 @@ function handleMouseOverMenu(menu, eventType) { toggleMenuVisibility(creditsMenu); } - else if(menu.classList.contains("help")) - { - helpDocumentation.classList.toggle("visible"); - } + } + if(eventType == "click" && menu.classList.contains("help")) + { + helpDocumentation.classList.toggle("visible"); } } diff --git a/hmml_to_html/style.css b/hmml_to_html/style.css index 926e2d4..40b49ed 100644 --- a/hmml_to_html/style.css +++ b/hmml_to_html/style.css @@ -15,7 +15,13 @@ } .title .episode_name { - flex: 1 1; + flex: 1; +} + +.title > #focus-warn { + color: #F00; + flex: 1; + margin: auto; } .title > .menu { @@ -53,17 +59,24 @@ margin: 2px; } +.help_key.unavailable, +.help_text.unavailable, +.help_container h2 .unavailable { + opacity: 0.32; +} + .key_block { display: inline-flex; align-items: flex-end; flex-direction: row; - margin-right: 4px; + margin: 8px; } .help_text { margin: 0 8px 0 2px; } + .help_container h1 { display: inline; margin-left: 4px;