past page QOL
This commit is contained in:
parent
20232f4abe
commit
99cb615161
@ -1,6 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using IF.Lastfm.Core.Objects;
|
||||||
using Microsoft.AspNetCore.SignalR;
|
using Microsoft.AspNetCore.SignalR;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Selector.Cache;
|
using Selector.Cache;
|
||||||
@ -49,6 +51,16 @@ namespace Selector.Web.Hubs
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> AlbumSuffixes = new[]
|
||||||
|
{
|
||||||
|
" (Deluxe)",
|
||||||
|
" (Deluxe Edition)",
|
||||||
|
" (Special)",
|
||||||
|
" (Special Edition)",
|
||||||
|
" (Expanded)",
|
||||||
|
" (Expanded Edition)",
|
||||||
|
};
|
||||||
|
|
||||||
public async Task OnSubmitted(PastParams param)
|
public async Task OnSubmitted(PastParams param)
|
||||||
{
|
{
|
||||||
param.Track = string.IsNullOrWhiteSpace(param.Track) ? null : param.Track;
|
param.Track = string.IsNullOrWhiteSpace(param.Track) ? null : param.Track;
|
||||||
@ -67,38 +79,49 @@ namespace Selector.Web.Hubs
|
|||||||
to: to
|
to: to
|
||||||
).ToArray();
|
).ToArray();
|
||||||
|
|
||||||
|
Parallel.ForEach(listenQuery, (listen) =>
|
||||||
|
{
|
||||||
|
foreach (var suffix in AlbumSuffixes)
|
||||||
|
{
|
||||||
|
if (listen.AlbumName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
listen.AlbumName = listen.AlbumName.Substring(0, listen.AlbumName.Length - suffix.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
var artistGrouped = listenQuery
|
var artistGrouped = listenQuery
|
||||||
.GroupBy(x => x.ArtistName)
|
.GroupBy(x => x.ArtistName.ToLowerInvariant())
|
||||||
.Select(x => (x.Key, x.Count()))
|
.Select(x => (x.Key, x.Count()))
|
||||||
.OrderByDescending(x => x.Item2)
|
.OrderByDescending(x => x.Item2)
|
||||||
.Take(20)
|
.Take(pastOptions.Value.RankingCount)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var albumGrouped = listenQuery
|
var albumGrouped = listenQuery
|
||||||
.GroupBy(x => (x.AlbumName, x.ArtistName))
|
.GroupBy(x => (x.AlbumName.ToLowerInvariant(), x.ArtistName.ToLowerInvariant()))
|
||||||
.Select(x => (x.Key, x.Count()))
|
.Select(x => (x.Key, x.Count(), $"{x.FirstOrDefault()?.AlbumName} // {x.FirstOrDefault()?.ArtistName}"))
|
||||||
.OrderByDescending(x => x.Item2)
|
.OrderByDescending(x => x.Item2)
|
||||||
.Take(20)
|
.Take(pastOptions.Value.RankingCount)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
var trackGrouped = listenQuery
|
var trackGrouped = listenQuery
|
||||||
.GroupBy(x => (x.TrackName, x.ArtistName))
|
.GroupBy(x => (x.TrackName.ToLowerInvariant(), x.ArtistName.ToLowerInvariant()))
|
||||||
.Select(x => (x.Key, x.Count()))
|
.Select(x => (x.Key, x.Count(), $"{x.FirstOrDefault()?.TrackName} // {x.FirstOrDefault()?.ArtistName}"))
|
||||||
.OrderByDescending(x => x.Item2)
|
.OrderByDescending(x => x.Item2)
|
||||||
.Take(20)
|
.Take(pastOptions.Value.RankingCount)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
await Clients.Caller.OnRankResult(new()
|
await Clients.Caller.OnRankResult(new()
|
||||||
{
|
{
|
||||||
TrackEntries = trackGrouped.Select(x => new ChartEntry()
|
TrackEntries = trackGrouped.Select(x => new ChartEntry()
|
||||||
{
|
{
|
||||||
Name = $"{x.Key.TrackName} - {x.Key.ArtistName}",
|
Name = x.Item3,
|
||||||
Value = x.Item2
|
Value = x.Item2
|
||||||
}).ToArray(),
|
}).ToArray(),
|
||||||
|
|
||||||
AlbumEntries = albumGrouped.Select(x => new ChartEntry()
|
AlbumEntries = albumGrouped.Select(x => new ChartEntry()
|
||||||
{
|
{
|
||||||
Name = $"{x.Key.AlbumName} - {x.Key.ArtistName}",
|
Name = x.Item3,
|
||||||
Value = x.Item2
|
Value = x.Item2
|
||||||
}).ToArray(),
|
}).ToArray(),
|
||||||
|
|
||||||
|
@ -9,9 +9,9 @@
|
|||||||
<div id="pastapp" class="app col-12">
|
<div id="pastapp" class="app col-12">
|
||||||
<div class="card" style="width: 100%">
|
<div class="card" style="width: 100%">
|
||||||
<div>
|
<div>
|
||||||
<input v-model="track" class="form-input form-control" placeholder="Track" />
|
<input v-model="track" class="form-input form-control" placeholder="Track" v-on:keyup.enter="submit" />
|
||||||
<input v-model="album" class="form-input form-control" placeholder="Album" />
|
<input v-model="album" class="form-input form-control" placeholder="Album" v-on:keyup.enter="submit" />
|
||||||
<input v-model="artist" class="form-input form-control" placeholder="Artist" />
|
<input v-model="artist" class="form-input form-control" placeholder="Artist" v-on:keyup.enter="submit" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label for="from-picker">From</label>
|
<label for="from-picker">From</label>
|
||||||
@ -27,7 +27,6 @@
|
|||||||
<count-card :count="totalCount"></count-card>
|
<count-card :count="totalCount"></count-card>
|
||||||
|
|
||||||
<play-count-chart-card :data_points="resampledSeries"
|
<play-count-chart-card :data_points="resampledSeries"
|
||||||
:title="'Time Series'"
|
|
||||||
:chart_id="'time_series'"
|
:chart_id="'time_series'"
|
||||||
:colour="'#ffffff'"
|
:colour="'#ffffff'"
|
||||||
v-if="resampledSeries.length > 0"></play-count-chart-card>
|
v-if="resampledSeries.length > 0"></play-count-chart-card>
|
||||||
@ -37,6 +36,11 @@
|
|||||||
<rank-card :title="'Album'" :entries="albumEntries" v-if="albumEntries.length > 1"></rank-card>
|
<rank-card :title="'Album'" :entries="albumEntries" v-if="albumEntries.length > 1"></rank-card>
|
||||||
<rank-card :title="'Artist'" :entries="artistEntries" v-if="artistEntries.length > 1"></rank-card>
|
<rank-card :title="'Artist'" :entries="artistEntries" v-if="artistEntries.length > 1"></rank-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<album-chart-card :data_points="mutatedAlbums"
|
||||||
|
:chart_id="'past'"
|
||||||
|
:colour="'#ffffff'"
|
||||||
|
v-if="albumEntries.length > 1"></album-chart-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ export let PlayCountChartCard: Vue.Component = {
|
|||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
yAxis: {
|
yAxis: {
|
||||||
suggestedMin: 0
|
beginAtZero: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
@ -112,7 +112,7 @@ export let CombinedPlayCountChartCard: Vue.Component = {
|
|||||||
},
|
},
|
||||||
scales: {
|
scales: {
|
||||||
yAxis: {
|
yAxis: {
|
||||||
suggestedMin: 0
|
beginAtZero: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
|
48
Selector.Web/scripts/Past/BarGraphCard.ts
Normal file
48
Selector.Web/scripts/Past/BarGraphCard.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import * as Vue from "vue";
|
||||||
|
import { Chart, BarElement, BarController } from "chart.js";
|
||||||
|
import 'chartjs-adapter-luxon';
|
||||||
|
import { CountSample, RankEntry } from "scripts/HubInterfaces";
|
||||||
|
|
||||||
|
Chart.register(BarElement, BarController);
|
||||||
|
|
||||||
|
export let BarChartCard: Vue.Component = {
|
||||||
|
props: ['data_points', 'title', 'chart_id', 'link', 'earliest_date', 'latest_date', 'colour'],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
chartData: {
|
||||||
|
datasets: [{
|
||||||
|
data: this.data_points.map((e: RankEntry) => {
|
||||||
|
return {x: e.name, y: e.value};
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
chartId() {
|
||||||
|
return "bar-chart-" + this.chart_id;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template:
|
||||||
|
`
|
||||||
|
<div class="chart-card card" style="width: 100%">
|
||||||
|
<h1>{{ title }}</h1>
|
||||||
|
<canvas :id="chartId"></canvas>
|
||||||
|
<lastfm-logo :link="link" v-if="link" />
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
mounted() {
|
||||||
|
new Chart(`bar-chart-${this.chart_id}`, {
|
||||||
|
type: "bar",
|
||||||
|
data: this.chartData,
|
||||||
|
options: {
|
||||||
|
backgroundColor: 'white',
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,21 @@ import * as Vue from "vue";
|
|||||||
export let CountCard: Vue.Component = {
|
export let CountCard: Vue.Component = {
|
||||||
props: ['count'],
|
props: ['count'],
|
||||||
computed: {
|
computed: {
|
||||||
|
formattedCount() {
|
||||||
|
if(this.count != null)
|
||||||
|
{
|
||||||
|
return this.count.toLocaleString()
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
template:
|
template:
|
||||||
`
|
`
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2>{{ count }}</h2>
|
<h2>{{ formattedCount }}</h2>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}
|
}
|
@ -6,9 +6,11 @@ import { RankCard } from "./Past/RankCard";
|
|||||||
import { CountCard } from "./Past/CountCard";
|
import { CountCard } from "./Past/CountCard";
|
||||||
import { PlayCountChartCard } from "./Now/PlayCountGraph";
|
import { PlayCountChartCard } from "./Now/PlayCountGraph";
|
||||||
import { LastFmLogoLink } from "./Now/LastFm";
|
import { LastFmLogoLink } from "./Now/LastFm";
|
||||||
|
import { BarChartCard } from "./Past/BarGraphCard";
|
||||||
|
|
||||||
const connection = new signalR.HubConnectionBuilder()
|
const connection = new signalR.HubConnectionBuilder()
|
||||||
.withUrl("/pasthub")
|
.withUrl("/pasthub")
|
||||||
|
.withAutomaticReconnect()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
connection.start()
|
connection.start()
|
||||||
@ -60,12 +62,26 @@ const app = Vue.createApp({
|
|||||||
|
|
||||||
console.log(context);
|
console.log(context);
|
||||||
|
|
||||||
|
this.trackEntries = [];
|
||||||
|
this.albumEntries = [];
|
||||||
|
this.artistEntries = [];
|
||||||
|
this.resampledSeries = [];
|
||||||
|
|
||||||
connection.invoke("OnSubmitted", context);
|
connection.invoke("OnSubmitted", context);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
computed: {
|
||||||
|
mutatedAlbums() {
|
||||||
|
return this.albumEntries.map((e: RankEntry) => {
|
||||||
|
e.name = e.name.split(' // ')[0];
|
||||||
|
return e;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
app.component("play-count-chart-card", PlayCountChartCard);
|
app.component("play-count-chart-card", PlayCountChartCard);
|
||||||
|
app.component("album-chart-card", BarChartCard);
|
||||||
app.component("rank-card", RankCard);
|
app.component("rank-card", RankCard);
|
||||||
app.component("lastfm-logo", LastFmLogoLink);
|
app.component("lastfm-logo", LastFmLogoLink);
|
||||||
app.component("count-card", CountCard);
|
app.component("count-card", CountCard);
|
||||||
|
@ -24,6 +24,7 @@ namespace Selector
|
|||||||
public const string Key = "Past";
|
public const string Key = "Past";
|
||||||
|
|
||||||
public TimeSpan ResampleWindow { get; set; } = TimeSpan.FromDays(7);
|
public TimeSpan ResampleWindow { get; set; } = TimeSpan.FromDays(7);
|
||||||
|
public int RankingCount { get; set; } = 20;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user