audio feature cards, added chart.js

This commit is contained in:
andy 2021-11-11 19:54:28 +00:00
parent bfb2a5d2cd
commit 4405cf885a
10 changed files with 193 additions and 38 deletions

View File

@ -13,7 +13,7 @@ namespace Selector.Cache
public class CachingAudioFeatureInjector : AudioFeatureInjector public class CachingAudioFeatureInjector : AudioFeatureInjector
{ {
private readonly IDatabaseAsync Db; private readonly IDatabaseAsync Db;
public TimeSpan CacheExpiry { get; set; } = TimeSpan.FromDays(1); public TimeSpan CacheExpiry { get; set; } = TimeSpan.FromDays(14);
public CachingAudioFeatureInjector( public CachingAudioFeatureInjector(
IPlayerWatcher watcher, IPlayerWatcher watcher,

View File

@ -15,6 +15,7 @@ namespace Selector.Cache
private readonly IPlayerWatcher Watcher; private readonly IPlayerWatcher Watcher;
private readonly IDatabaseAsync Db; private readonly IDatabaseAsync Db;
private readonly ILogger<CacheWriter> Logger; private readonly ILogger<CacheWriter> Logger;
public TimeSpan CacheExpiry { get; set; } = TimeSpan.FromMinutes(10);
public CancellationToken CancelToken { get; set; } public CancellationToken CancelToken { get; set; }
@ -43,7 +44,7 @@ namespace Selector.Cache
Logger.LogTrace($"Caching current for [{e.Id}/{e.SpotifyUsername}]"); Logger.LogTrace($"Caching current for [{e.Id}/{e.SpotifyUsername}]");
var resp = await Db.StringSetAsync(Key.CurrentlyPlaying(e.Id), payload); var resp = await Db.StringSetAsync(Key.CurrentlyPlaying(e.Id), payload, expiry: CacheExpiry);
Logger.LogDebug($"Cached current for [{e.Id}/{e.SpotifyUsername}], {(resp ? "value set" : "value NOT set")}"); Logger.LogDebug($"Cached current for [{e.Id}/{e.SpotifyUsername}], {(resp ? "value set" : "value NOT set")}");

View File

@ -1,7 +1,12 @@
.app {
display: block;
}
.card { .card {
background-color: grey; background-color: grey;
color: white; color: white;
margin: 5px; margin: 5px;
padding: 15px;
box-shadow: 4px 4px 2px #5e5e5e; box-shadow: 4px 4px 2px #5e5e5e;
} }
@ -13,20 +18,42 @@
.cover-art { .cover-art {
box-shadow: 4px 4px 2px #5e5e5e; box-shadow: 4px 4px 2px #5e5e5e;
margin-bottom: 15px;
} }
img { img {
margin: 15px; // margin: 15px;
}
}
.info-card {
margin-top: 10px;
margin-bottom: 10px;
img {
// margin: 15px;
} }
} }
@media only screen and (min-width: 768px) { @media only screen and (min-width: 768px) {
.app {
display: flex;
align-items: flex-start;
}
.now-playing-card { .now-playing-card {
position: fixed; position: fixed;
z-index: 10;
right: 20px; right: 20px;
bottom: 20px; bottom: 20px;
width: 300px; width: 250px;
}
}
@media only screen and (min-width: 768px) {
.info-card {
max-width: 300px;
} }
} }
@ -37,18 +64,3 @@
width: 21px; width: 21px;
} }
} }
.info-card {
margin-top: 10px;
margin-bottom: 10px;
img {
margin: 15px;
}
}
@media only screen and (min-width: 768px) {
.info-card {
max-width: 300px;
}
}

View File

@ -6,12 +6,14 @@
<div class="text-center"> <div class="text-center">
<h1 class="display-4">Now</h1> <h1 class="display-4">Now</h1>
<div id="app"> <div id="app" class="app col-12">
<now-playing-card :track="currentlyPlaying.track" v-if="currentlyPlaying !== null && currentlyPlaying !== undefined"></now-playing-card> <now-playing-card :track="currentlyPlaying.track" v-if="currentlyPlaying !== null && currentlyPlaying !== undefined" ></now-playing-card>
<now-playing-card v-else></now-playing-card> <now-playing-card v-else ></now-playing-card>
<popularity :track="currentlyPlaying.track" v-if="currentlyPlaying !== null && currentlyPlaying !== undefined && currentlyPlaying.track != null && currentlyPlaying.track != undefined" /> <popularity :track="currentlyPlaying.track" v-if="currentlyPlaying !== null && currentlyPlaying !== undefined && currentlyPlaying.track != null && currentlyPlaying.track != undefined" ></popularity>
<info-card v-for="card in cards" :html="card.html" /> <audio-feature-card :feature="trackFeatures" v-if="trackFeatures !== null && trackFeatures !== undefined" /></audio-feature-card>
<audio-feature-chart-card :feature="trackFeatures" v-if="trackFeatures !== null && trackFeatures !== undefined" /></audio-feature-chart-card>
<info-card v-for="card in cards" :html="card.html"></info-card>
</div> </div>
</div> </div>

View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@microsoft/signalr": "^5.0.11", "@microsoft/signalr": "^5.0.11",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"chart.js": "^3.6.0",
"vue": "^3.2.21" "vue": "^3.2.21"
}, },
"devDependencies": { "devDependencies": {
@ -943,6 +944,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/chart.js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.0.tgz",
"integrity": "sha512-iOzzDKePL+bj+ccIsVAgWQehCXv8xOKGbaU2fO/myivH736zcx535PGJzQGanvcSGVOqX6yuLZsN3ygcQ35UgQ=="
},
"node_modules/chownr": { "node_modules/chownr": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
@ -5175,6 +5181,11 @@
} }
} }
}, },
"chart.js": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-3.6.0.tgz",
"integrity": "sha512-iOzzDKePL+bj+ccIsVAgWQehCXv8xOKGbaU2fO/myivH736zcx535PGJzQGanvcSGVOqX6yuLZsN3ygcQ35UgQ=="
},
"chownr": { "chownr": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",

View File

@ -22,6 +22,7 @@
"dependencies": { "dependencies": {
"@microsoft/signalr": "^5.0.11", "@microsoft/signalr": "^5.0.11",
"bootstrap": "^5.1.3", "bootstrap": "^5.1.3",
"chart.js": "^3.6.0",
"vue": "^3.2.21" "vue": "^3.2.21"
}, },
"devDependencies": { "devDependencies": {

View File

@ -0,0 +1,31 @@
const keyStrings: string[] = [
"C",
"C#",
"D",
"D#",
"E",
"F",
"F#",
"G",
"G#",
"A",
"A#",
"B",
];
export function KeyString(keyCode: number): string
{
return keyStrings[keyCode];
}
export function ModeString(modeCode: number): string
{
if(modeCode === 1)
{
return "Major";
}
else {
return "Minor";
}
}

View File

@ -1,4 +1,8 @@
import * as Vue from "vue"; import * as Vue from "vue";
import { KeyString, ModeString } from "../Helper";
import { Chart, RadarController, RadialLinearScale, PointElement, LineElement } from "chart.js";
Chart.register(RadarController, RadialLinearScale, PointElement, LineElement);
export let PopularityCard: Vue.Component = { export let PopularityCard: Vue.Component = {
props: ['track'], props: ['track'],
@ -17,9 +21,108 @@ export let SpotifyLogoLink: Vue.Component = {
template: template:
` `
<a :href="link" target="_blank" class="spotify-logo" v-if="link != null && link != undefined"> <a :href="link" target="_blank" class="spotify-logo" v-if="link != null && link != undefined">
<img src="/Spotify_Icon_RGB_White.png"> <img src="/Spotify_Icon_RGB_White.png" >
</a> </a>
<img src="/Spotify_Icon_RGB_White.png" v-else> <img src="/Spotify_Icon_RGB_White.png" class="spotify-logo" v-else>
` `
} }
export let AudioFeatureCard: Vue.Component = {
props: ['feature'],
computed: {
Key(): string
{
return KeyString(this.feature.key);
},
Mode(): string
{
return ModeString(this.feature.mode);
}
},
template:
`
<div class="card info-card">
<h3>Info</h3>
<h5>Key: <b>{{ Key }} {{ Mode }}</b></h5>
<h5>Tempo: <b>{{ feature.tempo }} BPM</b></h5>
<h5>Time: <b>{{ feature.timeSignature }}/4</b></h5>
<h5>Loudness: <b>{{ feature.loudness }} dB</b></h5>
<spotify-logo />
</div>
`
}
export let AudioFeatureChartCard: Vue.Component = {
props: ['feature'],
data() {
return {
chartData: {
labels: [
'Energy',
'Dance',
'Speech',
'Live',
'Instrumental',
'Acoustic',
'Valence'
],
datasets: [{
// label: '# of Votes',
data: [
this.feature.energy,
this.feature.danceability,
this.feature.speechiness,
this.feature.liveness,
this.feature.instrumentalness,
this.feature.acousticness,
this.feature.valence
],
}]
}
}
},
template:
`
<div class="card info-card">
<canvas id="feature-chart"></canvas>
<spotify-logo />
</div>
`,
mounted() {
new Chart("feature-chart", {
type: "radar",
data: this.chartData,
options: {
// plugins: {
// legend: {
// labels: {
// color: "white"
// }
// }
// },
elements: {
line: {
borderWidth: 4
},
point: {
radius: 4
}
},
scales: {
r: {
angleLines: {
display: false
},
beginAtZero: true,
suggestedMin: 0,
suggestedMax: 1,
ticks: {
display: false
}
}
}
}
})
}
}

View File

@ -2,7 +2,7 @@ import * as signalR from "@microsoft/signalr";
import * as Vue from "vue"; import * as Vue from "vue";
import { TrackAudioFeatures, CurrentlyPlayingDTO } from "./HubInterfaces"; import { TrackAudioFeatures, CurrentlyPlayingDTO } from "./HubInterfaces";
import NowPlayingCard from "./Now/NowPlayingCard"; import NowPlayingCard from "./Now/NowPlayingCard";
import { PopularityCard, SpotifyLogoLink } from "./Now/Spotify"; import { AudioFeatureCard, AudioFeatureChartCard, PopularityCard, SpotifyLogoLink } from "./Now/Spotify";
import BaseInfoCard from "./Now/BaseInfoCard"; import BaseInfoCard from "./Now/BaseInfoCard";
const connection = new signalR.HubConnectionBuilder() const connection = new signalR.HubConnectionBuilder()
@ -38,6 +38,7 @@ const app = Vue.createApp({
{ {
console.log(context); console.log(context);
this.currentlyPlaying = context; this.currentlyPlaying = context;
this.trackFeatures = null;
this.cards = []; this.cards = [];
if(context.track !== null && context.track !== undefined) if(context.track !== null && context.track !== undefined)
@ -55,6 +56,8 @@ const app = Vue.createApp({
}); });
app.component("now-playing-card", NowPlayingCard); app.component("now-playing-card", NowPlayingCard);
app.component("audio-feature-card", AudioFeatureCard);
app.component("audio-feature-chart-card", AudioFeatureChartCard);
app.component("info-card", BaseInfoCard); app.component("info-card", BaseInfoCard);
app.component("popularity", PopularityCard); app.component("popularity", PopularityCard);
app.component("spotify-logo", SpotifyLogoLink); app.component("spotify-logo", SpotifyLogoLink);

View File

@ -10,15 +10,6 @@
"allowJs": true, "allowJs": true,
"moduleResolution": "node", "moduleResolution": "node",
"outDir": "./wwwroot/js", "outDir": "./wwwroot/js",
"baseUrl": ".", "baseUrl": "."
"rootDir": "./scripts", }
"paths": {
"@/*": [
"./scripts/*"
]
}
},
"include": [
"scripts/**/*"
]
} }