diff --git a/src/components/autoFocuser.js b/src/components/autoFocuser.js new file mode 100644 index 0000000000..09ded814f2 --- /dev/null +++ b/src/components/autoFocuser.js @@ -0,0 +1,101 @@ +define(["focusManager", "layoutManager"], function (focusManager, layoutManager) { + "use strict"; + + /** + * Previously selected element. + */ + var activeElement; + + /** + * Returns true if AutoFocuser is enabled. + */ + function isEnabled() { + return layoutManager.tv; + } + + /** + * Start AutoFocuser + */ + function enable() { + if (!isEnabled()) { + return; + } + + window.addEventListener("focusin", function (e) { + activeElement = e.target; + }); + + console.log("AutoFocuser enabled"); + } + + /** + * Create an array from some source. + */ + var arrayFrom = Array.prototype.from || function (src) { + return Array.prototype.slice.call(src); + } + + /** + * Set focus on a suitable element, taking into account the previously selected. + */ + function autoFocus(container) { + if (!isEnabled()) { + return; + } + + container = container || document.body; + + var candidates = []; + + if (activeElement) { + // These elements are recreated + if (activeElement.classList.contains("btnPreviousPage")) { + candidates.push(container.querySelector(".btnPreviousPage")); + candidates.push(container.querySelector(".btnNextPage")); + } else if (activeElement.classList.contains("btnNextPage")) { + candidates.push(container.querySelector(".btnNextPage")); + candidates.push(container.querySelector(".btnPreviousPage")); + } else if (activeElement.classList.contains("btnSelectView")) { + candidates.push(container.querySelector(".btnSelectView")); + } + + candidates.push(activeElement); + } + + candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnResume"))); + candidates = candidates.concat(arrayFrom(container.querySelectorAll(".btnPlay"))); + + var focusedElement; + + candidates.every(function (element) { + if (focusManager.isCurrentlyFocusable(element)) { + focusManager.focus(element); + focusedElement = element; + return false; + } + + return true; + }); + + if (!focusedElement) { + // FIXME: Multiple itemsContainers + var itemsContainer = container.querySelector(".itemsContainer"); + + if (itemsContainer) { + focusedElement = focusManager.autoFocus(itemsContainer); + } + } + + if (!focusedElement) { + focusedElement = focusManager.autoFocus(container); + } + + return focusedElement; + } + + return { + isEnabled: isEnabled, + enable: enable, + autoFocus: autoFocus + }; +}); diff --git a/src/controllers/addserver.js b/src/controllers/addserver.js index 55b670f546..a55ba3066c 100644 --- a/src/controllers/addserver.js +++ b/src/controllers/addserver.js @@ -46,6 +46,10 @@ define(["appSettings", "loading", "browser", "emby-button"], function(appSetting view.querySelector(".addServerForm").addEventListener("submit", onServerSubmit); view.querySelector(".btnCancel").addEventListener("click", goBack); + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(view); + }); + function onServerSubmit(e) { submitServer(view); e.preventDefault(); diff --git a/src/controllers/itemdetailpage.js b/src/controllers/itemdetailpage.js index e086ace309..1ce35efe4c 100644 --- a/src/controllers/itemdetailpage.js +++ b/src/controllers/itemdetailpage.js @@ -612,24 +612,9 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild hideAll(page, "btnDownload", true); } - try { - require(["focusManager"], function (focusManager) { - [".btnResume", ".btnPlay"].every(function (cls) { - var elems = page.querySelectorAll(cls); - - for (var i = 0; i < elems.length; i++) { - if (focusManager.isCurrentlyFocusable(elems[i])) { - focusManager.focus(elems[i]); - return false; - } - } - - return true; - }); - }); - } catch (e) { - console.log(e); - } + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); } function logoImageUrl(item, apiClient, options) { @@ -1724,6 +1709,12 @@ define(["loading", "appRouter", "layoutManager", "connectionManager", "cardBuild hideAll(page, "btnPlay", false); hideAll(page, "btnShuffle", false); } + + // HACK: Call autoFocuser again because btnPlay may be hidden, but focused by reloadFromItem + // FIXME: Sometimes focus does not move until all (?) sections are loaded + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); } function renderCollectionItemType(page, parentItem, type, items) { diff --git a/src/controllers/livetv/livetvchannels.js b/src/controllers/livetv/livetvchannels.js index 3e310a17a3..2de7e81843 100644 --- a/src/controllers/livetv/livetvchannels.js +++ b/src/controllers/livetv/livetvchannels.js @@ -102,6 +102,10 @@ define(["cardBuilder", "imageLoader", "libraryBrowser", "loading", "events", "em renderChannels(context, result); loading.hide(); isLoading = false; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(view); + }); }); } diff --git a/src/controllers/livetv/livetvsuggested.js b/src/controllers/livetv/livetvsuggested.js index 509b7f521c..8f65df7e6f 100644 --- a/src/controllers/livetv/livetvsuggested.js +++ b/src/controllers/livetv/livetvsuggested.js @@ -49,6 +49,10 @@ define(["layoutManager", "userSettings", "inputManager", "loading", "globalize", showAirEndTime: true }); loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); }); } diff --git a/src/controllers/loginpage.js b/src/controllers/loginpage.js index ef54d20bf7..8dd041a9f1 100644 --- a/src/controllers/loginpage.js +++ b/src/controllers/loginpage.js @@ -116,6 +116,10 @@ define(["apphost", "appSettings", "dom", "connectionManager", "loading", "layout view.querySelector(".visualLoginForm").classList.remove("hide"); view.querySelector(".manualLoginForm").classList.add("hide"); view.querySelector(".btnManual").classList.remove("hide"); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(view); + }); } view.querySelector("#divUsers").addEventListener("click", function(e) { diff --git a/src/controllers/movies/moviecollections.js b/src/controllers/movies/moviecollections.js index d5bd96d349..f17f64b7c8 100644 --- a/src/controllers/movies/moviecollections.js +++ b/src/controllers/movies/moviecollections.js @@ -172,6 +172,10 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); }); } diff --git a/src/controllers/movies/movies.js b/src/controllers/movies/movies.js index b4e30602fd..3a365acc90 100644 --- a/src/controllers/movies/movies.js +++ b/src/controllers/movies/movies.js @@ -77,6 +77,10 @@ define(["loading", "layoutManager", "userSettings", "events", "libraryBrowser", isLoading = false; loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(tabContent); + }); } function getItemsHtml(items) { diff --git a/src/controllers/movies/moviesrecommended.js b/src/controllers/movies/moviesrecommended.js index 82c0b6a12d..0327217e44 100644 --- a/src/controllers/movies/moviesrecommended.js +++ b/src/controllers/movies/moviesrecommended.js @@ -36,6 +36,9 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" showYear: true, centerText: true }); + + // FIXME: Wait for all sections to load + autoFocus(page); }); } @@ -76,6 +79,9 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" showYear: true, centerText: true }); + + // FIXME: Wait for all sections to load + autoFocus(page); }); } @@ -147,6 +153,15 @@ define(["events", "layoutManager", "inputManager", "userSettings", "libraryMenu" var recs = page.querySelector(".recommendations"); recs.innerHTML = html; imageLoader.lazyChildren(recs); + + // FIXME: Wait for all sections to load + autoFocus(page); + }); + } + + function autoFocus(page) { + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); }); } diff --git a/src/controllers/music/musicalbums.js b/src/controllers/music/musicalbums.js index ac45d9a15b..0cfb61e2b6 100644 --- a/src/controllers/music/musicalbums.js +++ b/src/controllers/music/musicalbums.js @@ -160,6 +160,10 @@ define(["layoutManager", "playbackManager", "loading", "events", "libraryBrowser libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); isLoading = false; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(tabContent); + }); }); } diff --git a/src/controllers/music/musicartists.js b/src/controllers/music/musicartists.js index 5159f635f8..aaeea8c885 100644 --- a/src/controllers/music/musicartists.js +++ b/src/controllers/music/musicartists.js @@ -144,6 +144,10 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(tabContent); + }); }); } diff --git a/src/controllers/music/musicgenres.js b/src/controllers/music/musicgenres.js index cf0052f5b1..b465a4d350 100644 --- a/src/controllers/music/musicgenres.js +++ b/src/controllers/music/musicgenres.js @@ -87,6 +87,10 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(context); + }); }); } diff --git a/src/controllers/music/musicplaylists.js b/src/controllers/music/musicplaylists.js index 964896077b..fd458c88ac 100644 --- a/src/controllers/music/musicplaylists.js +++ b/src/controllers/music/musicplaylists.js @@ -58,6 +58,10 @@ define(["libraryBrowser", "cardBuilder", "apphost", "imageLoader", "loading"], f imageLoader.lazyChildren(elem); libraryBrowser.saveQueryValues(getSavedQueryKey(), query); loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(context); + }); }); } diff --git a/src/controllers/music/musicrecommended.js b/src/controllers/music/musicrecommended.js index fc3371833d..556877aa1b 100644 --- a/src/controllers/music/musicrecommended.js +++ b/src/controllers/music/musicrecommended.js @@ -59,6 +59,10 @@ define(["browser", "layoutManager", "userSettings", "inputManager", "loading", " }); imageLoader.lazyChildren(elem); loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); }); } diff --git a/src/controllers/music/songs.js b/src/controllers/music/songs.js index 4806195edd..47263be0d2 100644 --- a/src/controllers/music/songs.js +++ b/src/controllers/music/songs.js @@ -104,6 +104,10 @@ define(["events", "libraryBrowser", "imageLoader", "listView", "loading", "emby- libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); }); } diff --git a/src/controllers/shows/episodes.js b/src/controllers/shows/episodes.js index 42d9d89f7a..9c61ac80d8 100644 --- a/src/controllers/shows/episodes.js +++ b/src/controllers/shows/episodes.js @@ -144,6 +144,10 @@ define(["loading", "events", "libraryBrowser", "imageLoader", "listView", "cardB libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); }); } diff --git a/src/controllers/shows/tvlatest.js b/src/controllers/shows/tvlatest.js index 2a1ed56bf4..5862fce45d 100644 --- a/src/controllers/shows/tvlatest.js +++ b/src/controllers/shows/tvlatest.js @@ -40,6 +40,10 @@ define(["loading", "components/groupedcards", "cardBuilder", "apphost", "imageLo elem.innerHTML = html; imageLoader.lazyChildren(elem); loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(context); + }); }); } diff --git a/src/controllers/shows/tvrecommended.js b/src/controllers/shows/tvrecommended.js index 1386e76a31..d1adb04342 100644 --- a/src/controllers/shows/tvrecommended.js +++ b/src/controllers/shows/tvrecommended.js @@ -97,6 +97,10 @@ define(["events", "inputManager", "libraryMenu", "layoutManager", "loading", "do cardLayout: false }); loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(view); + }); }); } diff --git a/src/controllers/shows/tvshows.js b/src/controllers/shows/tvshows.js index adccd98234..1dec530540 100644 --- a/src/controllers/shows/tvshows.js +++ b/src/controllers/shows/tvshows.js @@ -172,6 +172,10 @@ define(["layoutManager", "loading", "events", "libraryBrowser", "imageLoader", " libraryBrowser.saveQueryValues(getSavedQueryKey(page), query); loading.hide(); isLoading = false; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); }); } diff --git a/src/controllers/shows/tvstudios.js b/src/controllers/shows/tvstudios.js index 3c000a8e72..4e715e1fd7 100644 --- a/src/controllers/shows/tvstudios.js +++ b/src/controllers/shows/tvstudios.js @@ -46,6 +46,10 @@ define(["loading", "libraryBrowser", "cardBuilder", "apphost"], function (loadin context: "tvshows" }); loading.hide(); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(context); + }); }); } diff --git a/src/controllers/user/display.js b/src/controllers/user/display.js index f91e874a89..f348f28750 100644 --- a/src/controllers/user/display.js +++ b/src/controllers/user/display.js @@ -1,4 +1,4 @@ -define(["displaySettings", "userSettingsBuilder", "userSettings"], function (DisplaySettings, userSettingsBuilder, currentUserSettings) { +define(["displaySettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (DisplaySettings, userSettingsBuilder, currentUserSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -24,7 +24,8 @@ define(["displaySettings", "userSettingsBuilder", "userSettings"], function (Dis element: view.querySelector(".settingsContainer"), userSettings: userSettings, enableSaveButton: false, - enableSaveConfirmation: false + enableSaveConfirmation: false, + autoFocus: autoFocuser.isEnabled() }); } }); diff --git a/src/controllers/user/home.js b/src/controllers/user/home.js index 5794d58723..7f12efc7fb 100644 --- a/src/controllers/user/home.js +++ b/src/controllers/user/home.js @@ -1,4 +1,4 @@ -define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "listViewStyle"], function (HomescreenSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings) { +define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (HomescreenSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -24,7 +24,8 @@ define(["homescreenSettings", "userSettingsBuilder", "dom", "globalize", "loadin element: view.querySelector(".homeScreenSettingsContainer"), userSettings: userSettings, enableSaveButton: false, - enableSaveConfirmation: false + enableSaveConfirmation: false, + autoFocus: autoFocuser.isEnabled() }); } }); diff --git a/src/controllers/user/menu.js b/src/controllers/user/menu.js index d9fa2ab998..4e0a7824b7 100644 --- a/src/controllers/user/menu.js +++ b/src/controllers/user/menu.js @@ -35,6 +35,10 @@ define(["apphost", "connectionManager", "listViewStyle", "emby-button"], functio page.querySelector(".adminSection").classList.add("hide"); } }); + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(view); + }); }); }; }); diff --git a/src/controllers/user/playback.js b/src/controllers/user/playback.js index f2463ad8df..3def9d1931 100644 --- a/src/controllers/user/playback.js +++ b/src/controllers/user/playback.js @@ -1,4 +1,4 @@ -define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "listViewStyle"], function (PlaybackSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings) { +define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading", "userSettings", "autoFocuser", "listViewStyle"], function (PlaybackSettings, userSettingsBuilder, dom, globalize, loading, currentUserSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -24,7 +24,8 @@ define(["playbackSettings", "userSettingsBuilder", "dom", "globalize", "loading" element: view.querySelector(".settingsContainer"), userSettings: userSettings, enableSaveButton: false, - enableSaveConfirmation: false + enableSaveConfirmation: false, + autoFocus: autoFocuser.isEnabled() }); } }); diff --git a/src/controllers/user/subtitles.js b/src/controllers/user/subtitles.js index 205265efcd..1f1102194e 100644 --- a/src/controllers/user/subtitles.js +++ b/src/controllers/user/subtitles.js @@ -1,4 +1,4 @@ -define(["subtitleSettings", "userSettingsBuilder", "userSettings"], function (SubtitleSettings, userSettingsBuilder, currentUserSettings) { +define(["subtitleSettings", "userSettingsBuilder", "userSettings", "autoFocuser"], function (SubtitleSettings, userSettingsBuilder, currentUserSettings, autoFocuser) { "use strict"; return function (view, params) { @@ -24,7 +24,8 @@ define(["subtitleSettings", "userSettingsBuilder", "userSettings"], function (Su element: view.querySelector(".settingsContainer"), userSettings: userSettings, enableSaveButton: false, - enableSaveConfirmation: false + enableSaveConfirmation: false, + autoFocus: autoFocuser.isEnabled() }); } }); diff --git a/src/controllers/userpasswordpage.js b/src/controllers/userpasswordpage.js index 30ca063278..eeb9b25e3e 100644 --- a/src/controllers/userpasswordpage.js +++ b/src/controllers/userpasswordpage.js @@ -47,6 +47,10 @@ define(["loading", "libraryMenu", "emby-button"], function (loading, libraryMenu } page.querySelector(".chkEnableLocalEasyPassword").checked = user.Configuration.EnableLocalPassword; + + require(["autoFocuser"], function (autoFocuser) { + autoFocuser.autoFocus(page); + }); }); }); page.querySelector("#txtCurrentPassword").value = ""; diff --git a/src/scripts/site.js b/src/scripts/site.js index e05712c076..840d9e1b48 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -477,6 +477,9 @@ var AppInfo = {}; require(["keyboardnavigation"], function(keyboardnavigation) { keyboardnavigation.enable(); }); + require(["autoFocuser"], function(autoFocuser) { + autoFocuser.enable(); + }); }); }); } @@ -863,6 +866,7 @@ var AppInfo = {}; define("serverNotifications", [componentsPath + "/serverNotifications/serverNotifications"], returnFirstDependency); define("skinManager", [componentsPath + "/skinManager"], returnFirstDependency); define("keyboardnavigation", [componentsPath + "/keyboardnavigation"], returnFirstDependency); + define("autoFocuser", [componentsPath + "/autoFocuser"], returnFirstDependency); define("connectionManager", [], function () { return ConnectionManager; });