audio feature cards, added chart.js
This commit is contained in:
parent
bfb2a5d2cd
commit
4405cf885a
@ -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,
|
||||||
|
@ -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")}");
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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>
|
||||||
|
|
||||||
|
11
Selector.Web/package-lock.json
generated
11
Selector.Web/package-lock.json
generated
@ -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",
|
||||||
|
@ -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": {
|
||||||
|
31
Selector.Web/scripts/Helper.ts
Normal file
31
Selector.Web/scripts/Helper.ts
Normal 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";
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
@ -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/**/*"
|
|
||||||
]
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user