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 @@
-
-
+