diff --git a/js/index.js b/js/index.js index d0136b3..42a3ff0 100644 --- a/js/index.js +++ b/js/index.js @@ -212,9 +212,8 @@ if (Const.APP_DATES_CSV) httpGet(Const.APP_DATES_CSV).then(csv=>{ appSortInfo[key].created = parseDate(l[1]); appSortInfo[key].modified = parseDate(l[2]); }); - document.querySelector(".sort-nav").classList.remove("hidden"); - document.querySelector(".sort-nav label[sortid='created']").classList.remove("hidden"); - document.querySelector(".sort-nav label[sortid='modified']").classList.remove("hidden"); + document.querySelector(".sort-nav a[sortid='created']").parentElement.classList.remove("hidden"); + document.querySelector(".sort-nav a[sortid='modified']").parentElement.classList.remove("hidden"); }).catch(err=>{ console.log("No recent.csv - app sort disabled"); }); @@ -716,9 +715,6 @@ function getAppHTML(app, appInstalled, forInterface) { // =========================================== Library -// Can't use chip.attributes.filterid.value here because Safari/Apple's WebView doesn't handle it -let chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip => chip.getAttribute("filterid")); - /* Filter types: .../BangleApps/#blue shows apps having "blue" in app.id or app.tag --> searchType:hash @@ -731,14 +727,27 @@ let chips = Array.from(document.querySelectorAll('.filter-nav .chip')).map(chip */ -let activeSort = ''; +let activeSort = 'favourites'; let libraryShowAll = false; // perist whether user chose to view all apps // Update the sort state to match the current sort value function refreshSort(){ let sortContainer = document.querySelector("#librarycontainer .sort-nav"); - sortContainer.querySelector('.active').classList.remove('active'); - if(activeSort) sortContainer.querySelector('.chip[sortid="'+activeSort+'"]').classList.add('active'); - else sortContainer.querySelector('.chip[sortid]').classList.add('active'); + let sortToggle = sortContainer.querySelector('.dropdown-toggle span'); + let sortAnchors = sortContainer.querySelectorAll('.menu-item a'); + + // Find the currently selected sort and update label + let activeAnchor = Array.from(sortAnchors).find(a => + a.getAttribute('sortid') === (activeSort || '') + ); + + if (activeAnchor && sortToggle) { + sortToggle.innerHTML = ''; + if (activeSort === ''||!activeSort) { + sortToggle.innerHTML += `None`; + } else { + sortToggle.innerHTML += activeAnchor.textContent; + } + } } function handlefavouriteClick(icon,app,button){ const favAnimMS = 500; // duration of favourite animation in ms @@ -787,18 +796,25 @@ function refreshLibrary(options) { searchChip = searchParams.get("c").toLowerCase(); } } - if (searchType === "hash" && chips.indexOf(searchValue)>=0) { + if (searchType === "hash") { + // Treat URL hash as a chip/filter identifier + searchChip = searchValue; searchType = ""; searchValue = ""; - searchChip = searchValue; } - // Update the 'chips' to match the current window location let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); - filtersContainer.querySelector('.active').classList.remove('active'); - if(searchChip) { - let hashFilter = filtersContainer.querySelector('.chip[filterid="'+searchChip+'"]'); - if (hashFilter) hashFilter.classList.add('active'); - } else filtersContainer.querySelector('.chip[filterid]').classList.add('active'); + let filterToggle = filtersContainer.querySelector('.dropdown-toggle span'); + let filterAnchors = filtersContainer.querySelectorAll('.menu-item a'); + + // Find the currently selected filter and update label + let activeFilterAnchor = Array.from(filterAnchors).find(a => + a.getAttribute('dt') === (searchChip || '') + ); + + if (activeFilterAnchor && filterToggle) { + filterToggle.innerHTML = ''; + filterToggle.innerHTML += activeFilterAnchor.textContent; + } // update the search box value if (!options.dontChangeSearchBox) { if (searchType === "full") @@ -872,14 +888,25 @@ function refreshLibrary(options) { }).map(a => a.app); } // if not otherwise sorted, use 'sort by' option - if (!sortedByRelevance) - visibleApps.sort(appSorter); + if (!sortedByRelevance) { + if (activeSort === 'random') { + // Shuffle apps for "Explore" mode using Fisher-Yates + for (let i = visibleApps.length - 1; i > 0; i--) { + let j = Math.floor(Math.random() * (i + 1)); + let t = visibleApps[i]; visibleApps[i] = visibleApps[j]; visibleApps[j] = t; + } + } else { + visibleApps.sort(appSorter); + } + } if (activeSort && !sortedByRelevance) { // only sort if not searching (searching already sorts) if (["created","modified","installs","favourites"].includes(activeSort)) { visibleApps = visibleApps.sort((a,b) => ((appSortInfo[b.id]||{})[activeSort]||0) - ((appSortInfo[a.id]||{})[activeSort]||0)); + } else if (activeSort === 'random') { + // nothing to do - shuffled above } else throw new Error("Unknown sort type "+activeSort); } @@ -1450,19 +1477,21 @@ connectMyDeviceBtn.addEventListener("click", () => { }); Comms.watchConnectionChange(handleConnectionChange); -// Handle the 'chips' let filtersContainer = document.querySelector("#librarycontainer .filter-nav"); filtersContainer.addEventListener('click', ({ target }) => { - if (target.classList.contains('active')) return; - let filterName = target.getAttribute('filterid') || ''; - // Update window URL + // Only handle anchor clicks in menu items + if (target.tagName !== 'A' || !target.hasAttribute('dt')) return; + + let filterName = target.getAttribute('dt') || ''; window.history.replaceState(null, null, "?c=" + filterName); refreshLibrary(); }); let sortContainer = document.querySelector("#librarycontainer .sort-nav"); sortContainer.addEventListener('click', ({ target }) => { - if (target.classList.contains('active')) return; + // Only handle anchor clicks in menu items + if (target.tagName !== 'A' || !target.hasAttribute('sortid')) return; + activeSort = target.getAttribute('sortid') || ''; refreshSort(); refreshLibrary(); @@ -1515,6 +1544,70 @@ settingsCheckbox("settings-alwaysAllowEmulator", "alwaysAllowEmulator"); settingsCheckbox("settings-autoReload", "autoReload"); settingsCheckbox("settings-nopacket", "noPackets"); loadSettings(); +refreshSort(); + + +function autoAlignMenu(dropdown) { + const menu = dropdown.querySelector('.menu'); + if (!menu) return; + + // Ensure we can measure even if hidden + const prevVis = menu.style.visibility; + const prevDisp = menu.style.display; + const cs = getComputedStyle(menu); + if (cs.display === 'none') { + menu.style.visibility = 'hidden'; + menu.style.display = 'block'; + } + + // Reset to left before measuring + menu.classList.remove('align-right'); + + const rect = menu.getBoundingClientRect(); + const overflowRight = rect.right > (window.innerWidth - 8); // 8px margin + if (overflowRight) menu.classList.add('align-right'); + + // Restore styles + menu.style.visibility = prevVis || ''; + menu.style.display = prevDisp || ''; +} + +function closeDropdowns() { + document.querySelectorAll('.dropdown.active').forEach(dropdown => { + dropdown.classList.remove('active'); + const toggle = dropdown.querySelector('.dropdown-toggle'); + if (toggle && document.activeElement === toggle) { + requestAnimationFrame(() => toggle.blur()); + } + }); +} + +// Toggle dropdowns on header click +document.addEventListener('click', (e) => { + const toggle = e.target.closest('.dropdown-toggle'); + if (toggle) { + e.preventDefault(); + const dropdown = toggle.closest('.dropdown'); + if (!dropdown) return; + const shouldOpen = !dropdown.classList.contains('active'); + closeDropdowns(); + if (shouldOpen) { + dropdown.classList.add('active'); + requestAnimationFrame(() => autoAlignMenu(dropdown)); + } + return; + } + + closeDropdowns(); +}); + +// Keep alignment on resize +window.addEventListener('resize', () => { + document.querySelectorAll('.dropdown .menu').forEach(menu => { + const dropdown = menu.closest('.dropdown'); + if (dropdown) autoAlignMenu(dropdown); + }); +}); let btn;