diff --git a/src/assets/js/script.js b/src/assets/js/script.js index 78d90e2..d0fa27e 100644 --- a/src/assets/js/script.js +++ b/src/assets/js/script.js @@ -13,7 +13,7 @@ let loadingReady = false; let loadingStatus = loadingReady ? "Ready to Explr!" : "Loading..."; let announcementIntervalId; let noCountryArtistSortMethod = "scrobbles"; - +let selectedPeriod = "12month"; // Default period var STORED_ARTISTS; var STORED_ARTISTS_PROMISE = localforage.getItem("artists").then(val => @@ -25,11 +25,10 @@ var CACHED_NO_COUNTRIES_PROMISE = localforage.getItem("no_countries").then(val = CACHED_NO_COUNTRIES = val || {} ); -var USER_TAGS = []; // JSON.parse(window.localStorage.user_tags || "[]"); +var USER_TAGS = []; var CACHED_USERS = JSON.parse(window.localStorage.cached_users || "{}"); var SESSION = {}; - function clearExplrCache() { var theme = window.localStorage.getItem("theme"); window.localStorage.clear(); @@ -42,24 +41,76 @@ function clearExplrCache() { var countryCountObj = {}; +// Helper function to get display name for period +function getPeriodDisplayName(period) { + const periodNames = { + 'overall': 'All Time', + '12month': 'Last 12 Months', + '6month': 'Last 6 Months', + '3month': 'Last 3 Months', + '1month': 'Last Month', + '7day': 'Last 7 Days' + }; + return periodNames[period] || 'Last 12 Months'; +} + +// Helper function to create cache key with period +function getCacheKey(user, period) { + return `${user}_${period}`; +} + +// Helper function to get URL parameters +function getUrlParams() { + var params = {}; + var urlParts = window.location.href.split('?')[1]; + if (urlParts) { + var pairs = urlParts.split('&'); + pairs.forEach(function(pair) { + var keyValue = pair.split('='); + if (keyValue.length === 2) { + params[keyValue[0]] = decodeURIComponent(keyValue[1]); + } + }); + } + return params; +} + +// Initialize period from URL parameters early - FIXED +function initializePeriod() { + var urlParams = getUrlParams(); + if (urlParams.period) { + selectedPeriod = urlParams.period; + console.log("Period set from URL:", selectedPeriod); // Debug log + } +} + +// Call initializePeriod immediately when script loads +initializePeriod(); + (function () { - // user = prompt("Input your user name, get top 20 artists") - var user, currPage = 1, - maxPage; + var user, currPage = 1, maxPage; var count = 0; var tries = 0; var randomcountrylist = ["Malawi", "Malaysia", "Peru", "Sierra Leone", "Trinidad & Tobago", "Greece", "Laos", "Iran", "Haiti", "Nicaragua", "Mongolia", "Slovakia"]; var getAllArtists = function () { - // console.log("get artists") - loadingReady = false; - api.lastfm.send("library.getartists", [ - ["user", user], - ["limit", 50], - ["page", currPage] - ], + // Use the selected period when fetching artists + var apiParams = [ + ["user", user], + ["limit", 50], + ["page", currPage] + ]; + + // Add period parameter if not overall + if (selectedPeriod && selectedPeriod !== 'overall') { + apiParams.push(["period", selectedPeriod]); + } + + console.log("API params for getAllArtists:", apiParams); // Debug log + + api.lastfm.send("user.gettopartists", apiParams, function (error, responseData) { // Special case for unfortunate users if (responseData === "") { @@ -74,8 +125,6 @@ var countryCountObj = {}; // Try again, but not forever if (tries++ < 5) { getAllArtists(); - - // TODO: Show erorr message ;) } else { var refresh = confirm("Last.fm took too long to respond.\n\nPress OK to refresh the page and try again, or Cancel to use the page as it is."); if (refresh) { @@ -92,14 +141,14 @@ var countryCountObj = {}; tries = 0; if (currPage === 1) { - SESSION.total_artists = +responseData.artists["@attr"].total; - maxPage = +responseData.artists["@attr"].totalPages; + SESSION.total_artists = +responseData.topartists["@attr"].total; + maxPage = +responseData.topartists["@attr"].totalPages; if (SESSION.total_artists === 0) { d3.select(".bubblingG").remove(); d3.select("#loading-text") .html("You haven't listened to any
artists yet. Start scrobbling with
\ - your favorite music player!"); d3.select(".loader").style("pointer-events", "all"); return; @@ -107,11 +156,10 @@ var countryCountObj = {}; } currPage++; - // console.log("Artists done, get countries"); // Save artist data to localStorage (and create a list of artist names) var artistNames = [] - responseData.artists.artist.forEach(function (newArtist) { + responseData.topartists.artist.forEach(function (newArtist) { var a = STORED_ARTISTS[newArtist.name] || {}; a.playcount = +newArtist.playcount; @@ -121,15 +169,11 @@ var countryCountObj = {}; artistNames.push(newArtist.name); }) saveToStorage("artists", STORED_ARTISTS); - // var n = count++; // Get country for all artists api.getCountries(artistNames, function (data) { - //Gör så att man kan slå upp på land-id och få upp en lista på artister. var newArtistCountries = d3.nest().key((d) => d.id) - // gör så att man får en lista på alla artister för ett land. .rollup((leaves) => leaves) - // Skickar in en lista med ett objekt för varje artist. .map(data); d3.keys(newArtistCountries).forEach(function (id) { @@ -141,13 +185,10 @@ var countryCountObj = {}; artistsFromCountry = artistsFromCountry.concat(newArtistCountries[id]); artistsFromCountry.forEach(function (el, i) { - //Här lägger vi till ett fält image med artistens bild-url som ett fält till det "inre" objektet. artistsFromCountry[i].url = STORED_ARTISTS[el.artist].url; artistsFromCountry[i].playcount = STORED_ARTISTS[el.artist].playcount; }); - // countryCountObj är en lista med "country"-objekt. - // Varje country-objekt innehåller en lista med "inre" objekt med artistnamn, lands-id och landsnamn. - // dataObj är typ samma som countryCountObj, fast är bara för de tillfälligt sparade artisterna (intervallet). + countryCountObj[id][user] = artistsFromCountry; }) @@ -176,17 +217,13 @@ var countryCountObj = {}; }).rollup(function (d) { return d[0]; }).map(countriesList); - // Get "all" artists from one country - // countriesList.forEach(function(country){ - // }); api.lastfm.send("tag.gettopartists", [ ["tag", "swedish"], ["limit", limit], ["page", currPage] ], function (err, data) { var artists = data.topartists.artist; - // For each artist, get their tags artists.forEach(function (a) { api.lastfm.send("artist.gettoptags", [ ["artist", a.name] @@ -194,45 +231,31 @@ var countryCountObj = {}; // console.log(data); }) }) - // Look for user's top tags in artist tags - // If a lot of matches, save to recommended artists for that country }); - } var getUserTags = function (err, data) { - // err = err ||data.error; if (err || data.error) { if (data && data.error === 6) { alert("User not found"); window.location.assign(window.location.origin + window.location.pathname); } + return; } - - /*if (err || data.error) { - console.error("Erorr in getUserTags", err, data); - alert("Something went wrong when contacting the Last.fm API\n\nEither:\n - The specified user does not exist\n - Last.fm is down\n\nPlease try again."); - window.location.replace(window.location.origin + window.location.pathname); - }*/ - var c = 0; - var tagCount = {}; - //console.log("Gotta get tags") - var topArtists = data.topartists.artist; var done = function () { - // make list of tags to sort USER_TAGS = []; - //Remove specific tags from user's top tags let forbidden = ["american", "swedish", "british", "female vocalists", "male vocalists", "german", "seen live", "english", "singer-songwriter", "spanish", "french"]; d3.keys(tagCount).forEach(function (el) { var nogood = false for (let i = 0; i < forbidden.length; i++) { if (el === forbidden[i]) { nogood = true; + break; } } if (!nogood) { @@ -249,10 +272,8 @@ var countryCountObj = {}; window.localStorage.user_tags = JSON.stringify(USER_TAGS); } - topArtists.forEach(function (el, i) { - // get top ten tags and save to users tag count.... - setTimeout(function () { // Set timeout to not stop artists from loading... + setTimeout(function () { api.lastfm.send("artist.gettoptags", [ ["artist", el.name] ], function (err, data) { @@ -266,7 +287,6 @@ var countryCountObj = {}; tagCount[taglist[i].name] = 1; } } - // console.log(c, topArtists.length) } c++; @@ -276,13 +296,20 @@ var countryCountObj = {}; }); }, Math.random() * 3000); }); - } var begin = function () { - //Send analytics event - ga('send', 'event', 'splash screen', 'Go!', 'test'); - document.getElementById("map-label").innerHTML = `${user}'s world map`; + // Ensure we're using the correct period + SESSION.period = selectedPeriod; + + console.log("Begin function - selectedPeriod:", selectedPeriod); // Debug log + + // Send analytics event with period info + ga('send', 'event', 'splash screen', 'Go!', selectedPeriod); + + // Update map label with current period + document.getElementById("map-label").innerHTML = `${user}'s world map (${getPeriodDisplayName(selectedPeriod)})`; + // fade out username input box var welcomeOverlay = d3.select("#welcome-container"); welcomeOverlay.transition().duration(2000) @@ -291,9 +318,9 @@ var countryCountObj = {}; welcomeOverlay.remove(); }); - // Fade in loader + // Fade in loader with period info d3.select(".loader").transition().duration(2000).style("opacity", 1); - d3.select("#loading-text").html("Getting library..."); + d3.select("#loading-text").html(`Getting library (${getPeriodDisplayName(selectedPeriod)})...`); // Screen reader status update every 30 seconds setTimeout(function () { @@ -301,7 +328,7 @@ var countryCountObj = {}; }, 6000); setTimeout(function () { - if (d3.select("#loading-text")?.html() === "Getting library...") { + if (d3.select("#loading-text")?.html().includes("Getting library")) { d3.select("#loading-text").html("Last.fm is taking
a long time to
respond..."); setTimeout(function () { if (d3.select("#loading-text").html() === "Last.fm is taking
a long time to
respond...") { @@ -318,15 +345,19 @@ var countryCountObj = {}; // Fade in legend, progress-bar etc d3.selectAll(".on-map-view").style({ "visibility": "visible", - // "opacity": 0 - }) //.transition().duration(1000).style("opacity", 1); + }) - // Get user tags - api.lastfm.send("user.gettopartists", [ + // Get user tags with selected period - FIXED + var tagApiParams = [ ["user", user], - ["period", "12months"], ["limit", "50"] - ], getUserTags); + ]; + + if (selectedPeriod && selectedPeriod !== 'overall') { + tagApiParams.push(["period", selectedPeriod]); + } + + api.lastfm.send("user.gettopartists", tagApiParams, getUserTags); // Get user friends api.getFriends(function (err, data) { @@ -338,20 +369,17 @@ var countryCountObj = {}; var updateName = function () { friendName.html(""); friendName.append("a").attr({ - href: window.location.origin + window.location.pathname + "?username=" + friends[i].name, + href: window.location.origin + window.location.pathname + "?username=" + friends[i].name + "&period=" + selectedPeriod, target: "_self", }).html(friends[i].name); } d3.selectAll(".arrow").on("click", function () { if (d3.select(this).classed("left")) { - // Go left i = (i === 0 ? friends.length - 1 : i - 1); } else { - // Go right i = (i + 1) % friends.length; } - updateName(); }) @@ -366,23 +394,40 @@ var countryCountObj = {}; } }); - if (CACHED_USERS[user]) { - // TODO: use timestamp - console.info("No new artists on last.fm!"); - countryCountObj = JSON.parse(window.localStorage.countryCountObj); + // Check cache with period-specific key - FIXED CACHE LOGIC + var cacheKey = getCacheKey(user, selectedPeriod); + console.log("Checking cache for key:", cacheKey); // Debug log + + // IMPORTANT: Always fetch fresh data for different periods + // Only use cache if it's for the same period AND recent (within last hour) + var cacheTime = CACHED_USERS[cacheKey]; + var isRecentCache = cacheTime && (new Date().getTime() - cacheTime < 3600000); // 1 hour + + if (isRecentCache) { + console.info("Using cached data for period:", selectedPeriod); + var countryDataKey = `countryCountObj_${cacheKey}`; + countryCountObj = JSON.parse(window.localStorage.getItem(countryDataKey) || '{}'); localforage.getItem("no_countries", function (err, val) { noCountries.addArtistsWithNoCountry(val || []); }); // Get number of artists for screenshot etc. - api.lastfm.send("library.getartists", [ - ["user", user], - ["limit", 1], - ["page", 1] - ], + var apiParams = [ + ["user", user], + ["limit", 1], + ["page", 1] + ]; + + if (selectedPeriod && selectedPeriod !== 'overall') { + apiParams.push(["period", selectedPeriod]); + } + + api.lastfm.send("user.gettopartists", apiParams, function (error, responseData) { - SESSION.total_artists = +responseData.artists["@attr"].total; + if (!error && responseData && responseData.topartists) { + SESSION.total_artists = +responseData.topartists["@attr"].total; + } }); setTimeout(function () { @@ -394,12 +439,24 @@ var countryCountObj = {}; end(); }, 1000) } else { - // Save theme + console.info("Fetching fresh data for period:", selectedPeriod); + // Clear old cache data for different periods var theme = window.localStorage.theme; - window.localStorage.clear(); + + // Only clear artist cache, keep theme and other settings + Object.keys(localStorage).forEach(key => { + if (key.startsWith('countryCountObj_') || key === 'cached_users') { + localStorage.removeItem(key); + } + }); + if (theme) { window.localStorage.theme = theme; } + + // Reset CACHED_USERS for fresh start + CACHED_USERS = {}; + getAllArtists(); } } @@ -411,8 +468,10 @@ var countryCountObj = {}; clearInterval(announcementIntervalId); announcer.announce("All artists have been loaded!"); const map = document.querySelector("#map-container svg") - const existingAriaLabelledBy = map.getAttribute("aria-labelledby"); - map.setAttribute("aria-labelledby", `${existingAriaLabelledBy} progress-text sr-instructions`); + if (map) { + const existingAriaLabelledBy = map.getAttribute("aria-labelledby"); + map.setAttribute("aria-labelledby", `${existingAriaLabelledBy} progress-text sr-instructions`); + } // We're done, fade out loader var loader = d3.select(".loader"); @@ -421,68 +480,80 @@ var countryCountObj = {}; .each("end", function () { loader.remove(); }); - //Also fade out progress bar text (after a short delay) + + // Also fade out progress bar text (after a short delay) d3.select("#progress-text").transition().delay(5000).duration(1500) .style("opacity", 0); - CACHED_USERS = {}; - CACHED_USERS[user] = new Date().getTime(); + // Save cache with period-specific key + var cacheKey = getCacheKey(user, selectedPeriod); + CACHED_USERS[cacheKey] = new Date().getTime(); window.localStorage.cached_users = JSON.stringify(CACHED_USERS); - window.localStorage.countryCountObj = JSON.stringify(countryCountObj); + + // Save country data with period-specific key + var countryDataKey = `countryCountObj_${cacheKey}`; + window.localStorage.setItem(countryDataKey, JSON.stringify(countryCountObj)); + + console.log("Data saved for period:", selectedPeriod, "with key:", cacheKey); // Debug log } - // // Set theme - // map.nextTheme(window.localStorage.theme || "pink_white"); - - // Try to get username from url - var param = window.location.href.split("username=")[1]; + // Get URL parameters and set up initial values + var urlParams = getUrlParams(); + var param = urlParams.username; if (param) { // We already have a user - // Set up search button listener document.addEventListener('DOMContentLoaded', (event) => { document.getElementById('search-button').addEventListener('click', function() { - // Set timeout needed to make sure the browser is ready to focus the search box - setTimeout(()=> { search.initSearch() }, 0) ; + setTimeout(()=> { search.initSearch() }, 0); }); + + // Set up period selector if it exists + const periodSelect = document.getElementById("period-select"); + if (periodSelect) { + // Set initial value from current selectedPeriod + periodSelect.value = selectedPeriod; + + // Add change listener to reload with new period + periodSelect.addEventListener('change', function() { + const newPeriod = this.value; + const newUrl = window.location.pathname + '?username=' + param + '&period=' + newPeriod; + + // Always reload with new period + window.location.href = newUrl; + }); + } }); // set up keyboard shortcuts window.addEventListener("keydown", function (evt) { - if ((evt.ctrlKey || evt.metaKey) && evt.keyCode === 70 && !evt.shiftKey && !keyboardMode.getStatus()) { - console.log(keyboardMode.getStatus()); - // Prevent the browser's default "ctrl + f" or "cmd + f" action (usually "Find") evt.preventDefault(); - - // Initialize the search box search.initSearch(); - } + // Supress hotkeys if search or keyboard mode is open if (search.getSearchStatus() || keyboardMode.getStatus()) { return; }; + switch (evt.keyCode) { - case 83: + case 83: // 's' screenshot.render(); - //Send google analytics event ga('send', { hitType: 'event', eventCategory: 'Hotkeys', eventAction: 'Take screenshot', - eventLabel: 'test' + eventLabel: selectedPeriod }); break; - // t - case 84: + case 84: // 't' nextTheme(); - //Send google analytics event ga('send', { hitType: 'event', eventCategory: 'Hotkeys', eventAction: 'Cycle theme', - eventLabel: 'test' + eventLabel: selectedPeriod }); break; default: @@ -495,6 +566,8 @@ var countryCountObj = {}; } user = param; SESSION.name = param; + SESSION.period = selectedPeriod; + Promise.all([CACHED_NO_COUNTRIES_PROMISE, STORED_ARTISTS_PROMISE]).then(() => begin()); } else { d3.select("#welcome-container").style("visibility", "visible"); @@ -507,18 +580,32 @@ var countryCountObj = {}; })(); +// Export functions script.getCurrentData = function () { if (loadingReady) { - return JSON.parse(window.localStorage.getItem('countryCountObj'));; + var cacheKey = getCacheKey(SESSION.name, selectedPeriod); + var countryDataKey = `countryCountObj_${cacheKey}`; + return JSON.parse(window.localStorage.getItem(countryDataKey) || '{}'); } else { return countryCountObj; } - } script.getLoadingStatus = function () { return loadingStatus; } + script.setLoadingStatus = function (status) { loadingStatus = status; -} \ No newline at end of file +} + +script.getCurrentPeriod = function() { + return selectedPeriod; +} + +script.setPeriod = function(period) { + selectedPeriod = period; + if (SESSION) { + SESSION.period = period; + } +} diff --git a/src/index.html b/src/index.html index e5440bc..4d1bfa1 100644 --- a/src/index.html +++ b/src/index.html @@ -28,8 +28,35 @@ - - + - \ No newline at end of file + + \ No newline at end of file