// Storage for the options that the server gives us. This will define our filters. var options = {}; // Last time we updated the spots/alerts list on display. var lastUpdateTime; // Load and apply any URL params. This is used for "embedded mode" where another site can embed a version of // Spothole and provide its own interface options rather than using the user's saved ones. These may select things // from the various filter & display options, so this function needs to be called after these are set up, but if // the URL params ask for "embedded mode", this will suppress loading settings, so this needs to be called before // that occurs.. function loadURLParams() { let params = new URLSearchParams(document.location.search); // Handle embedded mode. We set a global to e.g. suppress loading/saving settings, and apply an attribute to the // top-level html element to use CSS selectors to remove bits of UI. let embedded = params.get("embedded"); if (embedded != null && embedded === "true") { useLocalStorage = false; $("html").attr("embedded-mode", "true"); } // Handle other params updateCheckboxFromParam(params, "dark-mode", "darkMode"); updateSelectFromParam(params, "time-zone", "timeZone"); // Only on Spots and Alerts pages updateSelectFromParam(params, "limit", "spots-to-fetch"); // Only on Spots page updateSelectFromParam(params, "limit", "alerts-to-fetch"); // Only on Alerts page updateSelectFromParam(params, "max_age", "max-spot-age"); // Only on Map & Bands pages updateFilterFromParam(params, "band", "band"); updateFilterFromParam(params, "sig", "sig"); updateFilterFromParam(params, "source", "source"); updateFilterFromParam(params, "mode", "mode"); updateFilterFromParam(params, "dx_continent", "dx_continent"); updateFilterFromParam(params, "de_continent", "de_continent"); } // Update an HTML checkbox element so that its selected matches the given parameter (which must have a true or false value) function updateCheckboxFromParam(params, paramName, checkboxID) { let v = params.get(paramName); if (v != null) { $("#" + checkboxID).prop("checked", (v === "true") ? true : false); // Extra check if this is the "dark mode" toggle if (checkboxID == "darkMode") { enableDarkMode((v === "true") ? true : false); } } } // Update an HTML select element so that its value matches the given parameter function updateSelectFromParam(params, paramName, selectID) { let v = params.get(paramName); if (v != null) { $("#" + selectID).prop("value", v); } } // Update a set of HTML checkbox elements describing a filter of the given name, so that any items named in the // parameter (as a comma-separated list) will be enabled, and all others disabled. e.g. if paramName is // "filter-band" and the params contain "filter-band=20m,40m", and prefix is "band", then #filter-button-band-30m // would be disabled but #filter-button-band-20m and #filter-button-band-40m would be enabled. function updateFilterFromParam(params, paramName, filterName) { let v = params.get(paramName); if (v != null) { // First uncheck all options for the filter $(".filter-button-" + filterName).prop("checked", false); // Now find out which ones should be enabled let s = v.split(","); s.forEach(val => $("#filter-button-" + filterName + "-" + val).prop("checked", true)); } } // For a parameter, such as dx_continent, get the query string for the current filter options. function getQueryStringFor(parameter) { return parameter + "=" + encodeURIComponent(getSelectedFilterOptions(parameter)); } // For a parameter, such as dx_continent, get the filter options that are currently selected in the UI. function getSelectedFilterOptions(parameter) { return $(".filter-button-" + parameter).filter(function() { return this.checked; }).map(function() { return this.value; }).get().join(","); } // For a parameter, such as dx_continent, return true if all possible options are enabled. (In this case, we don't need // to bother sending this as one of the query parameters to the API; no parameter provided implies "send everything".) function allFilterOptionsSelected(parameter) { var filter = $(".filter-button-" + parameter).filter(function() { return !this.checked; }).get(); return filter.length == 0; } // Generate a filter card with multiple toggle buttons plus All/None buttons. function generateMultiToggleFilterCard(elementID, filterQuery, options) { // Create a button for each option options.forEach(o => { $(elementID).append(` `); }); // Create All/None buttons $(elementID).append(`  `); } // Method called when "All" or "None" is clicked function toggleFilterButtons(filterQuery, state) { $(".filter-button-" + filterQuery).each(function() { $(this).prop('checked', state); }); filtersUpdated(); } // Update the refresh timing display function updateRefreshDisplay() { if (lastUpdateTime != null) { let secSinceUpdate = moment.duration(moment().diff(lastUpdateTime)).asSeconds(); let count = REFRESH_INTERVAL_SEC; let updatingString = "Updating..." if (secSinceUpdate < REFRESH_INTERVAL_SEC) { count = REFRESH_INTERVAL_SEC - secSinceUpdate; if (count <= 60) { var number = count.toFixed(0); updatingString = "Updating in " + number + " second" + (number != "1" ? "s" : "") + "."; } else { var number = Math.round(count / 60.0).toFixed(0); updatingString = "Updating in " + number + " minute" + (number != "1" ? "s" : "") + "."; } } $("#timing-container").html("Last updated at " + lastUpdateTime.format('HH:mm') + " UTC. " + updatingString); } } // When the "use local time" field is changed, reload the table and save settings function timeZoneUpdated() { updateTable(); saveSettings(); } // When one of the column toggle checkboxes are changed, reload the table and save settings function columnsUpdated() { updateTable(); saveSettings(); } // Function to set dark mode on or off function enableDarkMode(dark) { $("html").attr("data-bs-theme", dark ? "dark" : "light"); const metaThemeColor = document.querySelector("meta[name=theme-color]"); metaThemeColor.setAttribute("content", dark ? "black" : "white"); const metaAppleStatusBarStyle = document.querySelector("meta[name=apple-mobile-web-app-status-bar-style]"); metaAppleStatusBarStyle.setAttribute("content", dark ? "black-translucent" : "white-translucent"); } // Startup function to determine whether to use light or dark mode function usePreferredTheme() { // First, work out if we have ever explicitly saved the value of our toggle let val = localStorage.getItem("#darkMode:checked"); if (val != null) { enableDarkMode(JSON.parse(val)); } else { // Never set it before, so use the system default theme and set the toggle up to match let dark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; enableDarkMode(dark); $("#darkMode").prop('checked', dark); } } // Startup $(document).ready(function() { usePreferredTheme(); });