From 619effbf77a13acc10fb9218b52b10414ad5e2a9 Mon Sep 17 00:00:00 2001 From: robbevp Date: Sat, 4 Dec 2021 14:16:06 +0100 Subject: [PATCH] Add play stats page (#660) --- package.json | 1 + src/Main.vue | 4 + src/components/DateRangeSelect.vue | 139 ++++++++++++++++++++++++ src/components/PercentagePlayedCard.vue | 109 +++++++++++++++++++ src/components/TopTracksList.vue | 132 ++++++++++++++++++++++ src/filters.js | 6 + src/locales/en.json | 18 +++ src/locales/nl.json | 18 +++ src/main.js | 12 +- src/reducers.js | 23 ++++ src/router.js | 6 + src/views/App.vue | 10 ++ src/views/Stats.vue | 87 +++++++++++++++ yarn.lock | 5 + 14 files changed, 566 insertions(+), 4 deletions(-) create mode 100644 src/components/DateRangeSelect.vue create mode 100644 src/components/PercentagePlayedCard.vue create mode 100644 src/components/TopTracksList.vue create mode 100644 src/filters.js create mode 100644 src/reducers.js create mode 100644 src/views/Stats.vue diff --git a/package.json b/package.json index 9ba3fb90..5f1f38fc 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@mdi/font": "^6.5.95", "@mdi/svg": "^6.5.95", "fetch-retry": "^5.0.1", + "gsap": "^3.8.0", "localforage": "^1.10.0", "lodash.debounce": "^4.0.8", "roboto-fontface": "^0.10.0", diff --git a/src/Main.vue b/src/Main.vue index 63780b7b..610a6702 100644 --- a/src/Main.vue +++ b/src/Main.vue @@ -55,6 +55,10 @@ a { } // Additional utility classes +.break-word { + word-break: break-word; +} + .white-space-nowrap { white-space: nowrap; } diff --git a/src/components/DateRangeSelect.vue b/src/components/DateRangeSelect.vue new file mode 100644 index 00000000..2c792371 --- /dev/null +++ b/src/components/DateRangeSelect.vue @@ -0,0 +1,139 @@ + + + + + diff --git a/src/components/PercentagePlayedCard.vue b/src/components/PercentagePlayedCard.vue new file mode 100644 index 00000000..8b51de0c --- /dev/null +++ b/src/components/PercentagePlayedCard.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/TopTracksList.vue b/src/components/TopTracksList.vue new file mode 100644 index 00000000..ad2c6b55 --- /dev/null +++ b/src/components/TopTracksList.vue @@ -0,0 +1,132 @@ + + + + + diff --git a/src/filters.js b/src/filters.js new file mode 100644 index 00000000..c4682ff6 --- /dev/null +++ b/src/filters.js @@ -0,0 +1,6 @@ +export function filterPlaysByPeriod(startDate, endDate) { + return function (play) { + const playedAt = new Date(play.played_at); + return playedAt > startDate && playedAt < endDate; + }; +} diff --git a/src/locales/en.json b/src/locales/en.json index ce42c54a..7584e4f6 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -62,8 +62,21 @@ "replace": "Replace", "search": "Search", "settings": "Settings", + "stats": "Stats", "yes": "Yes" }, + "components": { + "dateRangeSelect": { + "label": "Select period", + "options": { + "last7Days": "Last 7 days", + "lastMonth": "Last month", + "thisYear": "This year", + "allTime": "All time", + "customRange": "Custom range" + } + } + }, "errors": { "album_artists": "Album artists", "albums": { @@ -346,6 +359,11 @@ "original": "Original quality" } }, + "stats": { + "percentageLibraryPlayed": "Percentage of library that you listened to", + "topTracks": "Top tracks", + "useTrackLength": "Use track length" + }, "users": { "auth": { "destroy-selected": "Delete selected", diff --git a/src/locales/nl.json b/src/locales/nl.json index dd82e381..5ed90ea1 100644 --- a/src/locales/nl.json +++ b/src/locales/nl.json @@ -62,8 +62,21 @@ "replace": "Vervangen", "search": "Zoeken", "settings": "Instellingen", + "stats": "Statistieken", "yes": "Ja" }, + "components": { + "dateRangeSelect": { + "label": "Selecteer periode", + "options": { + "last7Days": "Voorbije 7 dagen", + "lastMonth": "Voorbije maand", + "thisYear": "Dit jaar", + "allTime": "Altijd", + "customRange": "Eigen selectie" + } + } + }, "errors": { "album_artists": "Album artiesten", "albums": { @@ -342,6 +355,11 @@ "original": "Originele kwaliteit" } }, + "stats": { + "percentageLibraryPlayed": "Percentage van muziekbibliotheek dat je besluisterd hebt:", + "topTracks": "Meest besluisterde nummers", + "useTrackLength": "Volgens lengte nummers" + }, "users": { "auth": { "destroy-selected": "Verwijder geselecteerde", diff --git a/src/main.js b/src/main.js index 4aef3d40..e8237472 100644 --- a/src/main.js +++ b/src/main.js @@ -11,10 +11,14 @@ import colors from "vuetify/lib/util/colors"; Vue.config.productionTip = false; -Vue.filter( - "length", - (l) => `${Math.floor(l / 60)}:${`${l % 60}`.padStart(2, "0")}` -); +Vue.filter("length", (l) => { + const hours = Math.floor(l / 3600); + const minutes = Math.floor((l % 3600) / 60); + const seconds = `${l % 60}`.padStart(2, "0"); + return hours + ? `${hours}:${minutes.toString().padStart(2, "0")}:${seconds}` + : `${minutes}:${seconds}`; +}); Vue.use(Vuetify); Vue.use(Meta, { refreshOnceOnNavigation: true }); diff --git a/src/reducers.js b/src/reducers.js new file mode 100644 index 00000000..80f89592 --- /dev/null +++ b/src/reducers.js @@ -0,0 +1,23 @@ +export function calcPlayCountForTracks(plays) { + const acc = {}; + for (const play of plays) { + if (!(play.track_id in acc)) { + acc[play.track_id] = 1; + } else { + acc[play.track_id]++; + } + } + return acc; +} + +export function calcPlayTimeForTracks(plays, tracks) { + const acc = {}; + for (const play of plays) { + if (!(play.track_id in acc)) { + acc[play.track_id] = tracks[play.track_id]?.length || 0; + } else { + acc[play.track_id] += tracks[play.track_id]?.length || 0; + } + } + return acc; +} diff --git a/src/router.js b/src/router.js index 3459a57a..6d2946f8 100644 --- a/src/router.js +++ b/src/router.js @@ -13,6 +13,7 @@ import NewArtist from "./views/artists/NewArtist"; import Home from "./views/Home"; import Login from "./views/Login"; import Library from "./views/Library"; +import Stats from "./views/Stats"; import EditTrack from "./views/tracks/EditTrack"; import TracksWithoutAudio from "./views/tracks/TracksWithoutAudio"; import MergeTrack from "./views/tracks/MergeTrack"; @@ -134,6 +135,11 @@ const router = new Router({ name: "settings", component: Settings, }, + { + path: "stats", + name: "stats", + component: Stats, + }, { path: "tracks", name: "tracks", diff --git a/src/views/App.vue b/src/views/App.vue index d8a7d969..06edee0e 100644 --- a/src/views/App.vue +++ b/src/views/App.vue @@ -81,6 +81,16 @@ + + + mdi-chart-bar + + + + {{ $tc("common.stats", 2) }} + + + mdi-tune diff --git a/src/views/Stats.vue b/src/views/Stats.vue new file mode 100644 index 00000000..ae6f7969 --- /dev/null +++ b/src/views/Stats.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/yarn.lock b/yarn.lock index cef6c6bc..9e7bbc26 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4332,6 +4332,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" integrity sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ== +gsap@^3.8.0: + version "3.8.0" + resolved "https://registry.yarnpkg.com/gsap/-/gsap-3.8.0.tgz#a404dd6ebbaabc92605539aea9d98e3098688064" + integrity sha512-cvpzKkWFePDZCycwXwJnDSpTR3j+a4QLQF/t0c+pXqzRESgAYx5hieaoshzZFjbwsARqr0+5c3GKE7wI273w/g== + gzip-size@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274"