diff --git a/package.json b/package.json index bf3ac42f97..2631a4ad68 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "epubjs": "^0.3.85", "fast-text-encoding": "^1.0.3", "flv.js": "^1.5.0", + "focus-options-polyfill": "^1.5.0", "headroom.js": "^0.11.0", "hls.js": "^0.14.7", "howler": "^2.2.0", @@ -278,6 +279,7 @@ "src/plugins/backdropScreensaver/plugin.js", "src/plugins/bookPlayer/plugin.js", "src/plugins/bookPlayer/tableOfContents.js", + "src/plugins/chromecastPlayer/chromecastHelper.js", "src/plugins/photoPlayer/plugin.js", "src/plugins/youtubePlayer/plugin.js", "src/scripts/alphanumericshortcuts.js", diff --git a/src/bundle.js b/src/bundle.js index ae2a59f0d5..bebc80a181 100644 --- a/src/bundle.js +++ b/src/bundle.js @@ -10,6 +10,11 @@ _define('fetch', function() { return fetch; }); +var focusPreventScroll = require('focus-options-polyfill'); +_define('focus-options-polyfill', function() { + return focusPreventScroll; +}); + // Blurhash var blurhash = require('blurhash'); _define('blurhash', function() { diff --git a/src/legacy/focusPreventScroll.js b/src/legacy/focusPreventScroll.js deleted file mode 100644 index 97d6b0b120..0000000000 --- a/src/legacy/focusPreventScroll.js +++ /dev/null @@ -1,43 +0,0 @@ -// TODO: Move to external library (https://github.com/calvellido/focus-options-polyfill) -// Polyfill to add support for preventScroll by focus function - -if (HTMLElement.prototype.nativeFocus === undefined) { - (function () { - var supportsPreventScrollOption = false; - try { - var focusElem = document.createElement('div'); - - focusElem.addEventListener('focus', function(event) { - event.preventDefault(); - event.stopPropagation(); - }, true); - - var opts = Object.defineProperty({}, 'preventScroll', { - // eslint-disable-next-line getter-return - get: function () { - supportsPreventScrollOption = true; - } - }); - - focusElem.focus(opts); - } catch (e) { - console.error('error checking preventScroll support'); - } - - if (!supportsPreventScrollOption) { - HTMLElement.prototype.nativeFocus = HTMLElement.prototype.focus; - - HTMLElement.prototype.focus = function(options) { - var scrollX = window.scrollX; - var scrollY = window.scrollY; - - this.nativeFocus(); - - // Restore window scroll if preventScroll - if (options && options.preventScroll) { - window.scroll(scrollX, scrollY); - } - }; - } - })(); -} diff --git a/src/plugins/chromecastPlayer/chromecastHelper.js b/src/plugins/chromecastPlayer/chromecastHelper.js index 3fbff5507b..5893a5fe20 100644 --- a/src/plugins/chromecastPlayer/chromecastHelper.js +++ b/src/plugins/chromecastPlayer/chromecastHelper.js @@ -1,229 +1,229 @@ -define(['events'], function (events) { - 'use strict'; +import events from 'events'; - // LinkParser +// LinkParser +// +// https://github.com/ravisorg/LinkParser +// +// Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and +// IPv6, unrecognised TLDs, and more. +// +// This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. +// http://creativecommons.org/licenses/by-sa/4.0/ +(function () { + // Original URL regex from the Android android.text.util.Linkify function, found here: + // http://stackoverflow.com/a/19696443 // - // https://github.com/ravisorg/LinkParser + // However there were problems with it, most probably related to the fact it was + // written in 2007, and it's been highly modified. // - // Locate and extract almost any URL within a string. Handles protocol-less domains, IPv4 and - // IPv6, unrecognised TLDs, and more. + // 1) I didn't like the fact that it was tied to specific TLDs, since new ones + // are being added all the time it wouldn't be reasonable to expect developer to + // be continually updating their regular expressions. // - // This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License. - // http://creativecommons.org/licenses/by-sa/4.0/ - (function () { - // Original URL regex from the Android android.text.util.Linkify function, found here: - // http://stackoverflow.com/a/19696443 - // - // However there were problems with it, most probably related to the fact it was - // written in 2007, and it's been highly modified. - // - // 1) I didn't like the fact that it was tied to specific TLDs, since new ones - // are being added all the time it wouldn't be reasonable to expect developer to - // be continually updating their regular expressions. - // - // 2) It didn't allow unicode characters in the domains which are now allowed in - // many languages, (including some IDN TLDs). Again these are constantly being - // added to and it doesn't seem reasonable to hard-code them. Note this ended up - // not being possible in standard JS due to the way it handles multibyte strings. - // It is possible using XRegExp, however a big performance hit results. Disabled - // for now. - // - // 3) It didn't allow for IPv6 hostnames - // IPv6 regex from http://stackoverflow.com/a/17871737 - // - // 4) It was very poorly commented - // - // 5) It wasn't as smart as it could have been about what should be part of a - // URL and what should be part of human language. + // 2) It didn't allow unicode characters in the domains which are now allowed in + // many languages, (including some IDN TLDs). Again these are constantly being + // added to and it doesn't seem reasonable to hard-code them. Note this ended up + // not being possible in standard JS due to the way it handles multibyte strings. + // It is possible using XRegExp, however a big performance hit results. Disabled + // for now. + // + // 3) It didn't allow for IPv6 hostnames + // IPv6 regex from http://stackoverflow.com/a/17871737 + // + // 4) It was very poorly commented + // + // 5) It wasn't as smart as it could have been about what should be part of a + // URL and what should be part of human language. - var protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)'; - var credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters) - + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters) - + '\\@)'; + const protocols = '(?:(?:http|https|rtsp|ftp):\\/\\/)'; + const credentials = "(?:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,64}" // username (1-64 normal or url escaped characters) + + "(?:\\:(?:[a-z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-f0-9]{2})){1,25})?" // followed by optional password (: + 1-25 normal or url escaped characters) + + '\\@)'; - // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452 - // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License - // http://intermapper.com/ - var ipv6 = '(' - + '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))' - + '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' - + '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' - + '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' - + ')(%.+)?'; + // IPv6 Regex http://forums.intermapper.com/viewtopic.php?t=452 + // by Dartware, LLC is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License + // http://intermapper.com/ + const ipv6 = '(' + + '(([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))' + + '|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' + + '|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))' + + '|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + '|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))' + + ')(%.+)?'; - var ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.' - + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' - + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' - + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])'; + const ipv4 = '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.' + + '(?:25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9])'; - // This would have been a lot cleaner if JS RegExp supported conditionals... - var linkRegExpString = + // This would have been a lot cleaner if JS RegExp supported conditionals... + const linkRegExpString = - // begin match for protocol / username / password / host - '(?:' + // begin match for protocol / username / password / host + '(?:' - // ============================ - // If we have a recognized protocol at the beginning of the URL, we're - // more relaxed about what we accept, because we assume the user wants - // this to be a URL, and we're not accidentally matching human language - + protocols + '?' + // ============================ + // If we have a recognized protocol at the beginning of the URL, we're + // more relaxed about what we accept, because we assume the user wants + // this to be a URL, and we're not accidentally matching human language + + protocols + '?' - // optional username:password@ - + credentials + '?' + // optional username:password@ + + credentials + '?' - // IP address (both v4 and v6) - + '(?:' + // IP address (both v4 and v6) + + '(?:' - // IPv6 - + ipv6 + // IPv6 + + ipv6 - // IPv4 - + '|' + ipv4 + // IPv4 + + '|' + ipv4 - + ')' + + ')' - // end match for protocol / username / password / host - + ')' + // end match for protocol / username / password / host + + ')' - // optional port number - + '(?:\\:\\d{1,5})?' + // optional port number + + '(?:\\:\\d{1,5})?' - // plus optional path and query params (no unicode allowed here?) - + '(?:' - + '\\/(?:' - // some characters we'll accept because it's unlikely human language - // would use them after a URL unless they were part of the url - + '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])' - + '|(?:\\%[a-f0-9]{2})' - // some characters are much more likely to be used AFTER a url and - // were not intended to be included in the url itself. Mostly end - // of sentence type things. It's also likely that the URL would - // still work if any of these characters were missing from the end - // because we parsed it incorrectly. For these characters to be accepted - // they must be followed by another character that we're reasonably - // sure is part of the url - + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))" - + ')*' - + '|\\b|\$' - + ')'; + // plus optional path and query params (no unicode allowed here?) + + '(?:' + + '\\/(?:' + // some characters we'll accept because it's unlikely human language + // would use them after a URL unless they were part of the url + + '(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])' + + '|(?:\\%[a-f0-9]{2})' + // some characters are much more likely to be used AFTER a url and + // were not intended to be included in the url itself. Mostly end + // of sentence type things. It's also likely that the URL would + // still work if any of these characters were missing from the end + // because we parsed it incorrectly. For these characters to be accepted + // they must be followed by another character that we're reasonably + // sure is part of the url + + "|(?:[\\;\\?\\:\\.\\!\\'\\(\\)\\,\\=]+(?=(?:[a-z0-9\\/\\@\\&\\#\\~\\*\\_\\-\\+])|(?:\\%[a-f0-9]{2})))" + + ')*' + + '|\\b|\$' + + ')'; - // regex = XRegExp(regex,'gi'); - var linkRegExp = RegExp(linkRegExpString, 'gi'); + // regex = XRegExp(regex,'gi'); + const linkRegExp = RegExp(linkRegExpString, 'gi'); - var protocolRegExp = RegExp('^' + protocols, 'i'); + const protocolRegExp = RegExp('^' + protocols, 'i'); - // if url doesn't begin with a known protocol, add http by default - function ensureProtocol(url) { - if (!url.match(protocolRegExp)) { - url = 'http://' + url; - } - return url; + // if url doesn't begin with a known protocol, add http by default + function ensureProtocol(url) { + if (!url.match(protocolRegExp)) { + url = 'http://' + url; } + return url; + } - // look for links in the text - var LinkParser = { - parse: function (text) { - var links = []; - var match; + // look for links in the text + const LinkParser = { + parse: function (text) { + const links = []; + let match; - // eslint-disable-next-line no-cond-assign - while (match = linkRegExp.exec(text)) { - console.debug(match); - var txt = match[0]; - var pos = match.index; - var len = txt.length; - var url = ensureProtocol(text); - links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url }); - } - - return links; + // eslint-disable-next-line no-cond-assign + while (match = linkRegExp.exec(text)) { + console.debug(match); + const txt = match[0]; + const pos = match.index; + const len = txt.length; + const url = ensureProtocol(text); + links.push({ 'pos': pos, 'text': txt, 'len': len, 'url': url }); } - }; - - window.LinkParser = LinkParser; - })(); - - var cache = {}; - - function isValidIpAddress(address) { - var links = LinkParser.parse(address); - - return links.length == 1; - } - - function isLocalIpAddress(address) { - address = address.toLowerCase(); - - if (address.indexOf('127.0.0.1') !== -1) { - return true; - } - if (address.indexOf('localhost') !== -1) { - return true; + return links; } - return false; - } - - function getServerAddress(apiClient) { - var serverAddress = apiClient.serverAddress(); - - if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) { - return Promise.resolve(serverAddress); - } - - var cachedValue = getCachedValue(serverAddress); - if (cachedValue) { - return Promise.resolve(cachedValue); - } - - return apiClient.getEndpointInfo().then(function (endpoint) { - if (endpoint.IsInNetwork) { - return apiClient.getPublicSystemInfo().then(function (info) { - var localAddress = info.LocalAddress; - if (!localAddress) { - console.debug('No valid local address returned, defaulting to external one'); - localAddress = serverAddress; - } - addToCache(serverAddress, localAddress); - return localAddress; - }); - } else { - addToCache(serverAddress, serverAddress); - return serverAddress; - } - }); - } - - function clearCache() { - cache = {}; - } - - function addToCache(key, value) { - cache[key] = { - value: value, - time: new Date().getTime() - }; - } - - function getCachedValue(key) { - var obj = cache[key]; - - if (obj && (new Date().getTime() - obj.time) < 180000) { - return obj.value; - } - - return null; - } - - events.on(ConnectionManager, 'localusersignedin', clearCache); - events.on(ConnectionManager, 'localusersignedout', clearCache); - - return { - getServerAddress: getServerAddress }; -}); + + window.LinkParser = LinkParser; +})(); + +let cache = {}; + +// TODO: Replace with isIP (https://www.npmjs.com/package/is-ip) +function isValidIpAddress(address) { + const links = LinkParser.parse(address); + + return links.length == 1; +} + +// TODO: Add IPv6 support. Potentially replace with isLocalhost (https://www.npmjs.com/package/is-localhost-ip) +function isLocalIpAddress(address) { + address = address.toLowerCase(); + + if (address.includes('127.0.0.1')) { + return true; + } + if (address.includes('localhost')) { + return true; + } + + return false; +} + +function getServerAddress(apiClient) { + const serverAddress = apiClient.serverAddress(); + + if (isValidIpAddress(serverAddress) && !isLocalIpAddress(serverAddress)) { + return Promise.resolve(serverAddress); + } + + const cachedValue = getCachedValue(serverAddress); + if (cachedValue) { + return Promise.resolve(cachedValue); + } + + return apiClient.getEndpointInfo().then(function (endpoint) { + if (endpoint.IsInNetwork) { + return apiClient.getPublicSystemInfo().then(function (info) { + let localAddress = info.LocalAddress; + if (!localAddress) { + console.debug('No valid local address returned, defaulting to external one'); + localAddress = serverAddress; + } + addToCache(serverAddress, localAddress); + return localAddress; + }); + } else { + addToCache(serverAddress, serverAddress); + return serverAddress; + } + }); +} + +function clearCache() { + cache = {}; +} + +function addToCache(key, value) { + cache[key] = { + value: value, + time: new Date().getTime() + }; +} + +function getCachedValue(key) { + const obj = cache[key]; + + if (obj && (new Date().getTime() - obj.time) < 180000) { + return obj.value; + } + + return null; +} + +events.on(ConnectionManager, 'localusersignedin', clearCache); +events.on(ConnectionManager, 'localusersignedout', clearCache); + +export default { + getServerAddress: getServerAddress +}; diff --git a/src/plugins/chromecastPlayer/plugin.js b/src/plugins/chromecastPlayer/plugin.js index 22b49d6faf..4a4ce25600 100644 --- a/src/plugins/chromecastPlayer/plugin.js +++ b/src/plugins/chromecastPlayer/plugin.js @@ -366,6 +366,8 @@ define(['appSettings', 'userSettings', 'playbackManager', 'connectionManager', ' return new Promise(function (resolve, reject) { require(['./chromecastHelper'], function (chromecastHelper) { + chromecastHelper = chromecastHelper.default || chromecastHelper; + chromecastHelper.getServerAddress(apiClient).then(function (serverAddress) { message.serverAddress = serverAddress; player.sendMessageInternal(message).then(resolve, reject); diff --git a/src/scripts/gamepadtokey.js b/src/scripts/gamepadtokey.js index 870429ee09..b6f25f1eea 100644 --- a/src/scripts/gamepadtokey.js +++ b/src/scripts/gamepadtokey.js @@ -19,384 +19,383 @@ // # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // # THE SOFTWARE. -require(['apphost'], function (appHost) { - 'use strict'; - var _GAMEPAD_A_BUTTON_INDEX = 0; - var _GAMEPAD_B_BUTTON_INDEX = 1; - var _GAMEPAD_DPAD_UP_BUTTON_INDEX = 12; - var _GAMEPAD_DPAD_DOWN_BUTTON_INDEX = 13; - var _GAMEPAD_DPAD_LEFT_BUTTON_INDEX = 14; - var _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX = 15; - var _GAMEPAD_A_KEY = 'GamepadA'; - var _GAMEPAD_B_KEY = 'GamepadB'; - var _GAMEPAD_DPAD_UP_KEY = 'GamepadDPadUp'; - var _GAMEPAD_DPAD_DOWN_KEY = 'GamepadDPadDown'; - var _GAMEPAD_DPAD_LEFT_KEY = 'GamepadDPadLeft'; - var _GAMEPAD_DPAD_RIGHT_KEY = 'GamepadDPadRight'; - var _GAMEPAD_LEFT_THUMBSTICK_UP_KEY = 'GamepadLeftThumbStickUp'; - var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY = 'GamepadLeftThumbStickDown'; - var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY = 'GamepadLeftThumbStickLeft'; - var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY = 'GamepadLeftThumbStickRight'; - var _GAMEPAD_A_KEYCODE = 0; - var _GAMEPAD_B_KEYCODE = 27; - var _GAMEPAD_DPAD_UP_KEYCODE = 38; - var _GAMEPAD_DPAD_DOWN_KEYCODE = 40; - var _GAMEPAD_DPAD_LEFT_KEYCODE = 37; - var _GAMEPAD_DPAD_RIGHT_KEYCODE = 39; - var _GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE = 38; - var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE = 40; - var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE = 37; - var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE = 39; - var _THUMB_STICK_THRESHOLD = 0.75; +import appHost from 'apphost'; - var _leftThumbstickUpPressed = false; - var _leftThumbstickDownPressed = false; - var _leftThumbstickLeftPressed = false; - var _leftThumbstickRightPressed = false; - var _dPadUpPressed = false; - var _dPadDownPressed = false; - var _dPadLeftPressed = false; - var _dPadRightPressed = false; - var _gamepadAPressed = false; - var _gamepadBPressed = false; +var _GAMEPAD_A_BUTTON_INDEX = 0; +var _GAMEPAD_B_BUTTON_INDEX = 1; +var _GAMEPAD_DPAD_UP_BUTTON_INDEX = 12; +var _GAMEPAD_DPAD_DOWN_BUTTON_INDEX = 13; +var _GAMEPAD_DPAD_LEFT_BUTTON_INDEX = 14; +var _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX = 15; +var _GAMEPAD_A_KEY = 'GamepadA'; +var _GAMEPAD_B_KEY = 'GamepadB'; +var _GAMEPAD_DPAD_UP_KEY = 'GamepadDPadUp'; +var _GAMEPAD_DPAD_DOWN_KEY = 'GamepadDPadDown'; +var _GAMEPAD_DPAD_LEFT_KEY = 'GamepadDPadLeft'; +var _GAMEPAD_DPAD_RIGHT_KEY = 'GamepadDPadRight'; +var _GAMEPAD_LEFT_THUMBSTICK_UP_KEY = 'GamepadLeftThumbStickUp'; +var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY = 'GamepadLeftThumbStickDown'; +var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY = 'GamepadLeftThumbStickLeft'; +var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY = 'GamepadLeftThumbStickRight'; +var _GAMEPAD_A_KEYCODE = 0; +var _GAMEPAD_B_KEYCODE = 27; +var _GAMEPAD_DPAD_UP_KEYCODE = 38; +var _GAMEPAD_DPAD_DOWN_KEYCODE = 40; +var _GAMEPAD_DPAD_LEFT_KEYCODE = 37; +var _GAMEPAD_DPAD_RIGHT_KEYCODE = 39; +var _GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE = 38; +var _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE = 40; +var _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE = 37; +var _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE = 39; +var _THUMB_STICK_THRESHOLD = 0.75; - // The set of buttons on the gamepad we listen for. - var ProcessedButtons = [ - _GAMEPAD_DPAD_UP_BUTTON_INDEX, - _GAMEPAD_DPAD_DOWN_BUTTON_INDEX, - _GAMEPAD_DPAD_LEFT_BUTTON_INDEX, - _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX, - _GAMEPAD_A_BUTTON_INDEX, - _GAMEPAD_B_BUTTON_INDEX - ]; +var _leftThumbstickUpPressed = false; +var _leftThumbstickDownPressed = false; +var _leftThumbstickLeftPressed = false; +var _leftThumbstickRightPressed = false; +var _dPadUpPressed = false; +var _dPadDownPressed = false; +var _dPadLeftPressed = false; +var _dPadRightPressed = false; +var _gamepadAPressed = false; +var _gamepadBPressed = false; - var _ButtonPressedState = {}; - _ButtonPressedState.getgamepadA = function () { - return _gamepadAPressed; - }; +// The set of buttons on the gamepad we listen for. +var ProcessedButtons = [ + _GAMEPAD_DPAD_UP_BUTTON_INDEX, + _GAMEPAD_DPAD_DOWN_BUTTON_INDEX, + _GAMEPAD_DPAD_LEFT_BUTTON_INDEX, + _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX, + _GAMEPAD_A_BUTTON_INDEX, + _GAMEPAD_B_BUTTON_INDEX +]; - _ButtonPressedState.setgamepadA = function (newPressedState) { - raiseKeyEvent(_gamepadAPressed, newPressedState, _GAMEPAD_A_KEY, _GAMEPAD_A_KEYCODE, false, true); - _gamepadAPressed = newPressedState; - }; +var _ButtonPressedState = {}; +_ButtonPressedState.getgamepadA = function () { + return _gamepadAPressed; +}; - _ButtonPressedState.getgamepadB = function () { - return _gamepadBPressed; - }; +_ButtonPressedState.setgamepadA = function (newPressedState) { + raiseKeyEvent(_gamepadAPressed, newPressedState, _GAMEPAD_A_KEY, _GAMEPAD_A_KEYCODE, false, true); + _gamepadAPressed = newPressedState; +}; - _ButtonPressedState.setgamepadB = function (newPressedState) { - raiseKeyEvent(_gamepadBPressed, newPressedState, _GAMEPAD_B_KEY, _GAMEPAD_B_KEYCODE); - _gamepadBPressed = newPressedState; - }; +_ButtonPressedState.getgamepadB = function () { + return _gamepadBPressed; +}; - _ButtonPressedState.getleftThumbstickUp = function () { - return _leftThumbstickUpPressed; - }; +_ButtonPressedState.setgamepadB = function (newPressedState) { + raiseKeyEvent(_gamepadBPressed, newPressedState, _GAMEPAD_B_KEY, _GAMEPAD_B_KEYCODE); + _gamepadBPressed = newPressedState; +}; - _ButtonPressedState.setleftThumbstickUp = function (newPressedState) { - raiseKeyEvent(_leftThumbstickUpPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_UP_KEY, _GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE, true); - _leftThumbstickUpPressed = newPressedState; - }; +_ButtonPressedState.getleftThumbstickUp = function () { + return _leftThumbstickUpPressed; +}; - _ButtonPressedState.getleftThumbstickDown = function () { - return _leftThumbstickDownPressed; - }; +_ButtonPressedState.setleftThumbstickUp = function (newPressedState) { + raiseKeyEvent(_leftThumbstickUpPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_UP_KEY, _GAMEPAD_LEFT_THUMBSTICK_UP_KEYCODE, true); + _leftThumbstickUpPressed = newPressedState; +}; - _ButtonPressedState.setleftThumbstickDown = function (newPressedState) { - raiseKeyEvent(_leftThumbstickDownPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE, true); - _leftThumbstickDownPressed = newPressedState; - }; +_ButtonPressedState.getleftThumbstickDown = function () { + return _leftThumbstickDownPressed; +}; - _ButtonPressedState.getleftThumbstickLeft = function () { - return _leftThumbstickLeftPressed; - }; +_ButtonPressedState.setleftThumbstickDown = function (newPressedState) { + raiseKeyEvent(_leftThumbstickDownPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEY, _GAMEPAD_LEFT_THUMBSTICK_DOWN_KEYCODE, true); + _leftThumbstickDownPressed = newPressedState; +}; - _ButtonPressedState.setleftThumbstickLeft = function (newPressedState) { - raiseKeyEvent(_leftThumbstickLeftPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE, true); - _leftThumbstickLeftPressed = newPressedState; - }; +_ButtonPressedState.getleftThumbstickLeft = function () { + return _leftThumbstickLeftPressed; +}; - _ButtonPressedState.getleftThumbstickRight = function () { - return _leftThumbstickRightPressed; - }; +_ButtonPressedState.setleftThumbstickLeft = function (newPressedState) { + raiseKeyEvent(_leftThumbstickLeftPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEY, _GAMEPAD_LEFT_THUMBSTICK_LEFT_KEYCODE, true); + _leftThumbstickLeftPressed = newPressedState; +}; - _ButtonPressedState.setleftThumbstickRight = function (newPressedState) { - raiseKeyEvent(_leftThumbstickRightPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE, true); - _leftThumbstickRightPressed = newPressedState; - }; +_ButtonPressedState.getleftThumbstickRight = function () { + return _leftThumbstickRightPressed; +}; - _ButtonPressedState.getdPadUp = function () { - return _dPadUpPressed; - }; +_ButtonPressedState.setleftThumbstickRight = function (newPressedState) { + raiseKeyEvent(_leftThumbstickRightPressed, newPressedState, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEY, _GAMEPAD_LEFT_THUMBSTICK_RIGHT_KEYCODE, true); + _leftThumbstickRightPressed = newPressedState; +}; - _ButtonPressedState.setdPadUp = function (newPressedState) { - raiseKeyEvent(_dPadUpPressed, newPressedState, _GAMEPAD_DPAD_UP_KEY, _GAMEPAD_DPAD_UP_KEYCODE, true); - _dPadUpPressed = newPressedState; - }; +_ButtonPressedState.getdPadUp = function () { + return _dPadUpPressed; +}; - _ButtonPressedState.getdPadDown = function () { - return _dPadDownPressed; - }; +_ButtonPressedState.setdPadUp = function (newPressedState) { + raiseKeyEvent(_dPadUpPressed, newPressedState, _GAMEPAD_DPAD_UP_KEY, _GAMEPAD_DPAD_UP_KEYCODE, true); + _dPadUpPressed = newPressedState; +}; - _ButtonPressedState.setdPadDown = function (newPressedState) { - raiseKeyEvent(_dPadDownPressed, newPressedState, _GAMEPAD_DPAD_DOWN_KEY, _GAMEPAD_DPAD_DOWN_KEYCODE, true); - _dPadDownPressed = newPressedState; - }; +_ButtonPressedState.getdPadDown = function () { + return _dPadDownPressed; +}; - _ButtonPressedState.getdPadLeft = function () { - return _dPadLeftPressed; - }; +_ButtonPressedState.setdPadDown = function (newPressedState) { + raiseKeyEvent(_dPadDownPressed, newPressedState, _GAMEPAD_DPAD_DOWN_KEY, _GAMEPAD_DPAD_DOWN_KEYCODE, true); + _dPadDownPressed = newPressedState; +}; - _ButtonPressedState.setdPadLeft = function (newPressedState) { - raiseKeyEvent(_dPadLeftPressed, newPressedState, _GAMEPAD_DPAD_LEFT_KEY, _GAMEPAD_DPAD_LEFT_KEYCODE, true); - _dPadLeftPressed = newPressedState; - }; +_ButtonPressedState.getdPadLeft = function () { + return _dPadLeftPressed; +}; - _ButtonPressedState.getdPadRight = function () { - return _dPadRightPressed; - }; +_ButtonPressedState.setdPadLeft = function (newPressedState) { + raiseKeyEvent(_dPadLeftPressed, newPressedState, _GAMEPAD_DPAD_LEFT_KEY, _GAMEPAD_DPAD_LEFT_KEYCODE, true); + _dPadLeftPressed = newPressedState; +}; - _ButtonPressedState.setdPadRight = function (newPressedState) { - raiseKeyEvent(_dPadRightPressed, newPressedState, _GAMEPAD_DPAD_RIGHT_KEY, _GAMEPAD_DPAD_RIGHT_KEYCODE, true); - _dPadRightPressed = newPressedState; - }; +_ButtonPressedState.getdPadRight = function () { + return _dPadRightPressed; +}; - var times = {}; +_ButtonPressedState.setdPadRight = function (newPressedState) { + raiseKeyEvent(_dPadRightPressed, newPressedState, _GAMEPAD_DPAD_RIGHT_KEY, _GAMEPAD_DPAD_RIGHT_KEYCODE, true); + _dPadRightPressed = newPressedState; +}; - function throttle(key) { - var time = times[key] || 0; - var now = new Date().getTime(); +var times = {}; - if ((now - time) >= 200) { - //times[key] = now; - return true; - } - - return false; - } - - function resetThrottle(key) { - times[key] = new Date().getTime(); - } - - var isElectron = navigator.userAgent.toLowerCase().indexOf('electron') !== -1; - function allowInput() { - // This would be nice but always seems to return true with electron - if (!isElectron && document.hidden) { /* eslint-disable-line compat/compat */ - return false; - } - - if (appHost.getWindowState() === 'Minimized') { - return false; - } +function throttle(key) { + var time = times[key] || 0; + var now = new Date().getTime(); + if ((now - time) >= 200) { + //times[key] = now; return true; } - function raiseEvent(name, key, keyCode) { - if (!allowInput()) { - return; - } + return false; +} - var event = document.createEvent('Event'); - event.initEvent(name, true, true); - event.key = key; - event.keyCode = keyCode; - (document.activeElement || document.body).dispatchEvent(event); +function resetThrottle(key) { + times[key] = new Date().getTime(); +} + +var isElectron = navigator.userAgent.toLowerCase().indexOf('electron') !== -1; +function allowInput() { + // This would be nice but always seems to return true with electron + if (!isElectron && document.hidden) { /* eslint-disable-line compat/compat */ + return false; } - function clickElement(elem) { - if (!allowInput()) { - return; - } - - elem.click(); + if (appHost.getWindowState() === 'Minimized') { + return false; } - function raiseKeyEvent(oldPressedState, newPressedState, key, keyCode, enableRepeatKeyDown, clickonKeyUp) { - // No-op if oldPressedState === newPressedState - if (newPressedState === true) { - // button down - var fire = false; + return true; +} - // always fire if this is the initial down press - if (oldPressedState === false) { - fire = true; - resetThrottle(key); - } else if (enableRepeatKeyDown) { - fire = throttle(key); - } +function raiseEvent(name, key, keyCode) { + if (!allowInput()) { + return; + } - if (fire && keyCode) { - raiseEvent('keydown', key, keyCode); - } - } else if (newPressedState === false && oldPressedState === true) { + var event = document.createEvent('Event'); + event.initEvent(name, true, true); + event.key = key; + event.keyCode = keyCode; + (document.activeElement || document.body).dispatchEvent(event); +} + +function clickElement(elem) { + if (!allowInput()) { + return; + } + + elem.click(); +} + +function raiseKeyEvent(oldPressedState, newPressedState, key, keyCode, enableRepeatKeyDown, clickonKeyUp) { + // No-op if oldPressedState === newPressedState + if (newPressedState === true) { + // button down + var fire = false; + + // always fire if this is the initial down press + if (oldPressedState === false) { + fire = true; resetThrottle(key); + } else if (enableRepeatKeyDown) { + fire = throttle(key); + } - // button up - if (keyCode) { - raiseEvent('keyup', key, keyCode); - } - if (clickonKeyUp) { - clickElement(document.activeElement || window); - } + if (fire && keyCode) { + raiseEvent('keydown', key, keyCode); + } + } else if (newPressedState === false && oldPressedState === true) { + resetThrottle(key); + + // button up + if (keyCode) { + raiseEvent('keyup', key, keyCode); + } + if (clickonKeyUp) { + clickElement(document.activeElement || window); } } +} - var inputLoopTimer; - function runInputLoop() { - // Get the latest gamepad state. - var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ - for (var i = 0, len = gamepads.length; i < len; i++) { - var gamepad = gamepads[i]; - if (!gamepad) { - continue; - } - // Iterate through the axes - var axes = gamepad.axes; - var leftStickX = axes[0]; - var leftStickY = axes[1]; - if (leftStickX > _THUMB_STICK_THRESHOLD) { // Right - _ButtonPressedState.setleftThumbstickRight(true); - } else if (leftStickX < -_THUMB_STICK_THRESHOLD) { // Left - _ButtonPressedState.setleftThumbstickLeft(true); - } else if (leftStickY < -_THUMB_STICK_THRESHOLD) { // Up - _ButtonPressedState.setleftThumbstickUp(true); - } else if (leftStickY > _THUMB_STICK_THRESHOLD) { // Down - _ButtonPressedState.setleftThumbstickDown(true); - } else { - _ButtonPressedState.setleftThumbstickLeft(false); - _ButtonPressedState.setleftThumbstickRight(false); - _ButtonPressedState.setleftThumbstickUp(false); - _ButtonPressedState.setleftThumbstickDown(false); - } - // Iterate through the buttons to see if Left thumbstick, DPad, A and B are pressed. - var buttons = gamepad.buttons; - for (var j = 0, len = buttons.length; j < len; j++) { - if (ProcessedButtons.indexOf(j) !== -1) { - if (buttons[j].pressed) { - switch (j) { - case _GAMEPAD_DPAD_UP_BUTTON_INDEX: - _ButtonPressedState.setdPadUp(true); - break; - case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX: - _ButtonPressedState.setdPadDown(true); - break; - case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX: - _ButtonPressedState.setdPadLeft(true); - break; - case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX: - _ButtonPressedState.setdPadRight(true); - break; - case _GAMEPAD_A_BUTTON_INDEX: - _ButtonPressedState.setgamepadA(true); - break; - case _GAMEPAD_B_BUTTON_INDEX: - _ButtonPressedState.setgamepadB(true); - break; - default: - // No-op - break; - } - } else { - switch (j) { - case _GAMEPAD_DPAD_UP_BUTTON_INDEX: - if (_ButtonPressedState.getdPadUp()) { - _ButtonPressedState.setdPadUp(false); - } - break; - case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX: - if (_ButtonPressedState.getdPadDown()) { - _ButtonPressedState.setdPadDown(false); - } - break; - case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX: - if (_ButtonPressedState.getdPadLeft()) { - _ButtonPressedState.setdPadLeft(false); - } - break; - case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX: - if (_ButtonPressedState.getdPadRight()) { - _ButtonPressedState.setdPadRight(false); - } - break; - case _GAMEPAD_A_BUTTON_INDEX: - if (_ButtonPressedState.getgamepadA()) { - _ButtonPressedState.setgamepadA(false); - } - break; - case _GAMEPAD_B_BUTTON_INDEX: - if (_ButtonPressedState.getgamepadB()) { - _ButtonPressedState.setgamepadB(false); - } - break; - default: - // No-op - break; - } +var inputLoopTimer; +function runInputLoop() { + // Get the latest gamepad state. + var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ + for (var i = 0, len = gamepads.length; i < len; i++) { + var gamepad = gamepads[i]; + if (!gamepad) { + continue; + } + // Iterate through the axes + var axes = gamepad.axes; + var leftStickX = axes[0]; + var leftStickY = axes[1]; + if (leftStickX > _THUMB_STICK_THRESHOLD) { // Right + _ButtonPressedState.setleftThumbstickRight(true); + } else if (leftStickX < -_THUMB_STICK_THRESHOLD) { // Left + _ButtonPressedState.setleftThumbstickLeft(true); + } else if (leftStickY < -_THUMB_STICK_THRESHOLD) { // Up + _ButtonPressedState.setleftThumbstickUp(true); + } else if (leftStickY > _THUMB_STICK_THRESHOLD) { // Down + _ButtonPressedState.setleftThumbstickDown(true); + } else { + _ButtonPressedState.setleftThumbstickLeft(false); + _ButtonPressedState.setleftThumbstickRight(false); + _ButtonPressedState.setleftThumbstickUp(false); + _ButtonPressedState.setleftThumbstickDown(false); + } + // Iterate through the buttons to see if Left thumbstick, DPad, A and B are pressed. + var buttons = gamepad.buttons; + for (var j = 0, len = buttons.length; j < len; j++) { + if (ProcessedButtons.indexOf(j) !== -1) { + if (buttons[j].pressed) { + switch (j) { + case _GAMEPAD_DPAD_UP_BUTTON_INDEX: + _ButtonPressedState.setdPadUp(true); + break; + case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX: + _ButtonPressedState.setdPadDown(true); + break; + case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX: + _ButtonPressedState.setdPadLeft(true); + break; + case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX: + _ButtonPressedState.setdPadRight(true); + break; + case _GAMEPAD_A_BUTTON_INDEX: + _ButtonPressedState.setgamepadA(true); + break; + case _GAMEPAD_B_BUTTON_INDEX: + _ButtonPressedState.setgamepadB(true); + break; + default: + // No-op + break; + } + } else { + switch (j) { + case _GAMEPAD_DPAD_UP_BUTTON_INDEX: + if (_ButtonPressedState.getdPadUp()) { + _ButtonPressedState.setdPadUp(false); + } + break; + case _GAMEPAD_DPAD_DOWN_BUTTON_INDEX: + if (_ButtonPressedState.getdPadDown()) { + _ButtonPressedState.setdPadDown(false); + } + break; + case _GAMEPAD_DPAD_LEFT_BUTTON_INDEX: + if (_ButtonPressedState.getdPadLeft()) { + _ButtonPressedState.setdPadLeft(false); + } + break; + case _GAMEPAD_DPAD_RIGHT_BUTTON_INDEX: + if (_ButtonPressedState.getdPadRight()) { + _ButtonPressedState.setdPadRight(false); + } + break; + case _GAMEPAD_A_BUTTON_INDEX: + if (_ButtonPressedState.getgamepadA()) { + _ButtonPressedState.setgamepadA(false); + } + break; + case _GAMEPAD_B_BUTTON_INDEX: + if (_ButtonPressedState.getgamepadB()) { + _ButtonPressedState.setgamepadB(false); + } + break; + default: + // No-op + break; } } } } - // Schedule the next one - inputLoopTimer = requestAnimationFrame(runInputLoop); } + // Schedule the next one + inputLoopTimer = requestAnimationFrame(runInputLoop); +} - function startInputLoop() { - if (!inputLoopTimer) { - runInputLoop(); +function startInputLoop() { + if (!inputLoopTimer) { + runInputLoop(); + } +} + +function stopInputLoop() { + cancelAnimationFrame(inputLoopTimer); + inputLoopTimer = undefined; +} + +function isGamepadConnected() { + var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ + for (var i = 0, len = gamepads.length; i < len; i++) { + var gamepad = gamepads[i]; + if (gamepad && gamepad.connected) { + return true; } } + return false; +} - function stopInputLoop() { - cancelAnimationFrame(inputLoopTimer); - inputLoopTimer = undefined; +function onFocusOrGamepadAttach(e) { + /* eslint-disable-next-line compat/compat */ + if (isGamepadConnected() && document.hasFocus()) { + console.log('Gamepad connected! Starting input loop'); + startInputLoop(); } +} - function isGamepadConnected() { - var gamepads = navigator.getGamepads(); /* eslint-disable-line compat/compat */ - for (var i = 0, len = gamepads.length; i < len; i++) { - var gamepad = gamepads[i]; - if (gamepad && gamepad.connected) { - return true; - } - } - return false; +function onFocusOrGamepadDetach(e) { + /* eslint-disable-next-line compat/compat */ + if (!isGamepadConnected() || !document.hasFocus()) { + console.log('Gamepad disconnected! No other gamepads are connected, stopping input loop'); + stopInputLoop(); + } else { + console.log('Gamepad disconnected! There are gamepads still connected.'); } +} - function onFocusOrGamepadAttach(e) { - /* eslint-disable-next-line compat/compat */ - if (isGamepadConnected() && document.hasFocus()) { - console.log('Gamepad connected! Starting input loop'); - startInputLoop(); - } - } +// Event listeners for any change in gamepads' state. +window.addEventListener('gamepaddisconnected', onFocusOrGamepadDetach); +window.addEventListener('gamepadconnected', onFocusOrGamepadAttach); +window.addEventListener('blur', onFocusOrGamepadDetach); +window.addEventListener('focus', onFocusOrGamepadAttach); - function onFocusOrGamepadDetach(e) { - /* eslint-disable-next-line compat/compat */ - if (!isGamepadConnected() || !document.hasFocus()) { - console.log('Gamepad disconnected! No other gamepads are connected, stopping input loop'); - stopInputLoop(); - } else { - console.log('Gamepad disconnected! There are gamepads still connected.'); - } - } +onFocusOrGamepadAttach(); - // Event listeners for any change in gamepads' state. - window.addEventListener('gamepaddisconnected', onFocusOrGamepadDetach); - window.addEventListener('gamepadconnected', onFocusOrGamepadAttach); - window.addEventListener('blur', onFocusOrGamepadDetach); - window.addEventListener('focus', onFocusOrGamepadAttach); - - onFocusOrGamepadAttach(); - - // The gamepadInputEmulation is a string property that exists in JavaScript UWAs and in WebViews in UWAs. - // It won't exist in Win8.1 style apps or browsers. - if (window.navigator && typeof window.navigator.gamepadInputEmulation === 'string') { - // We want the gamepad to provide gamepad VK keyboard events rather than moving a - // mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events - // and provide input to the DOM navigator.getGamepads API. - window.navigator.gamepadInputEmulation = 'gamepad'; - } -}); +// The gamepadInputEmulation is a string property that exists in JavaScript UWAs and in WebViews in UWAs. +// It won't exist in Win8.1 style apps or browsers. +if (window.navigator && typeof window.navigator.gamepadInputEmulation === 'string') { + // We want the gamepad to provide gamepad VK keyboard events rather than moving a + // mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events + // and provide input to the DOM navigator.getGamepads API. + window.navigator.gamepadInputEmulation = 'gamepad'; +} diff --git a/src/scripts/site.js b/src/scripts/site.js index cd85c35e83..bf0fa75efa 100644 --- a/src/scripts/site.js +++ b/src/scripts/site.js @@ -689,7 +689,8 @@ function initClient() { 'events', 'credentialprovider', 'connectionManagerFactory', - 'appStorage' + 'appStorage', + 'focus-options-polyfill' ] }, urlArgs: urlArgs, @@ -698,7 +699,7 @@ function initClient() { }); promise = require(['fetch']) - .then(() => require(['jQuery', 'polyfill', 'fast-text-encoding', 'intersection-observer', 'classlist-polyfill', 'css!assets/css/site', 'jellyfin-noto'], (jQuery) => { + .then(() => require(['jQuery', 'polyfill', 'fast-text-encoding', 'intersection-observer', 'classlist-polyfill', 'focus-options-polyfill', 'css!assets/css/site', 'jellyfin-noto'], (jQuery) => { // Expose jQuery globally window.$ = jQuery; window.jQuery = jQuery; diff --git a/yarn.lock b/yarn.lock index 3ec7f21487..be5f329fa6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4608,6 +4608,11 @@ flv.js@^1.5.0: es6-promise "^4.2.5" webworkify "^1.5.0" +focus-options-polyfill@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/focus-options-polyfill/-/focus-options-polyfill-1.5.0.tgz#fd2e6a0d6d8f828346726a5adf02f3d2bdfece5f" + integrity sha512-HiMSaXGUz2OFjOuoGTWXlp+YjZCGnVXPu6vPeccgaSOzGmqLVz8tJRcKXWfMJnj16LXf/IM1rJI0zrZMVc9q7g== + follow-redirects@1.5.10: version "1.5.10" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"