diff --git a/jupyterlite_sphinx/jupyterlite_sphinx.css b/jupyterlite_sphinx/jupyterlite_sphinx.css index b62dd75..41db7f9 100644 --- a/jupyterlite_sphinx/jupyterlite_sphinx.css +++ b/jupyterlite_sphinx/jupyterlite_sphinx.css @@ -67,12 +67,3 @@ transform: rotate(1turn); } } - -/* we do not want the button to show on smaller screens (phones), as clicking - * can download a lot of data. 480px is a commonly used breakpoint to identify if a device is a smartphone. */ - -@media (max-width: 480px), (max-height: 480px) { - div.try_examples_button_container { - display: none; - } -} diff --git a/jupyterlite_sphinx/jupyterlite_sphinx.js b/jupyterlite_sphinx/jupyterlite_sphinx.js index 2304c86..9769e90 100644 --- a/jupyterlite_sphinx/jupyterlite_sphinx.js +++ b/jupyterlite_sphinx/jupyterlite_sphinx.js @@ -149,11 +149,71 @@ var tryExamplesGlobalMinHeight = 0; */ var tryExamplesConfigLoaded = false; +// This function is used to check if the current device is a mobile device. +// We assume the authenticity of the user agent string is enough to +// determine that, and we also check the window size as a fallback. +window.isMobileDevice = (() => { + let cachedUAResult = null; + let hasLogged = false; + + const checkUserAgent = () => { + if (cachedUAResult !== null) { + return cachedUAResult; + } + + const mobilePatterns = [ + /Android/i, + /webOS/i, + /iPhone/i, + /iPad/i, + /iPod/i, + /BlackBerry/i, + /IEMobile/i, + /Windows Phone/i, + /Opera Mini/i, + /SamsungBrowser/i, + /UC.*Browser|UCWEB/i, + /MiuiBrowser/i, + /Mobile/i, + /Tablet/i, + ]; + + cachedUAResult = mobilePatterns.some((pattern) => + pattern.test(navigator.userAgent), + ); + return cachedUAResult; + }; + + return () => { + const isMobileBySize = + window.innerWidth <= 480 || window.innerHeight <= 480; + const isLikelyMobile = checkUserAgent() || isMobileBySize; + + if (isLikelyMobile && !hasLogged) { + console.log( + "Either a mobile device detected or the screen was resized. Disabling interactive example buttons to conserve bandwidth.", + ); + hasLogged = true; + } + + return isLikelyMobile; + }; +})(); + // A config loader with request deduplication + permanent caching const ConfigLoader = (() => { let configLoadPromise = null; const loadConfig = async (configFilePath) => { + if (window.isMobileDevice()) { + const buttons = document.getElementsByClassName("try_examples_button"); + for (let i = 0; i < buttons.length; i++) { + buttons[i].classList.add("hidden"); + } + tryExamplesConfigLoaded = true; // mock it + return; + } + if (tryExamplesConfigLoaded) { return; } @@ -229,6 +289,27 @@ const ConfigLoader = (() => { }; })(); +// Add a resize handler that will update the buttons' visibility on +// orientation changes +let resizeTimeout; +window.addEventListener("resize", () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(() => { + if (!tryExamplesConfigLoaded) return; // since we won't interfere if the config isn't loaded + + const buttons = document.getElementsByClassName("try_examples_button"); + const shouldHide = window.isMobileDevice(); + + for (let i = 0; i < buttons.length; i++) { + if (shouldHide) { + buttons[i].classList.add("hidden"); + } else { + buttons[i].classList.remove("hidden"); + } + } + }, 250); +}); + window.loadTryExamplesConfig = ConfigLoader.loadConfig; window.toggleTryExamplesButtons = () => {