diff --git a/src/components/ListeningHistoryModal.tsx b/src/components/ListeningHistoryModal.tsx new file mode 100644 index 0000000..423fa73 --- /dev/null +++ b/src/components/ListeningHistoryModal.tsx @@ -0,0 +1,80 @@ +import styles from "../css/app.module.scss"; +import React from "react"; +import type { HistoricalMetrics } from "../types/enhancify"; +import getRecentlyPlayedTracksMetrics from "./../services/recentlyPlayedService" +import SongMetric from "./SongMetric"; + +class ListeningHistoryModal extends React.Component <{setModalIsOpen: (value: boolean) => void}, {metricsValues: HistoricalMetrics | {}}> { + state = { + metricsValues: { + acousticness: { + average: 0, + count: 0 + }, + danceability: { + average: 0, + count: 0 + }, + energy: { + average: 0, + count: 0 + }, + instrumentalness: { + average: 0, + count: 0 + }, + liveness: { + average: 0, + count: 0 + }, + speechiness: { + average: 0, + count: 0 + }, + valence: { + average: 0, + count: 0 + }, + } + } + + async componentDidMount(): Promise { + let newState = await getRecentlyPlayedTracksMetrics(); + this.setState({ + metricsValues: newState, + }); + } + + render() { + return ( +
+
+
+ {"Listening History"} +
+ this.props.setModalIsOpen(false)}/> +
+
+ {Object.keys(this.state.metricsValues).map((metric: string) => { + if (this.state.metricsValues[metric as keyof HistoricalMetrics].count) { + return {}} + >; + } else { + return <>; + } + })} +
+
); + } +} + +export default ListeningHistoryModal; \ No newline at end of file diff --git a/src/components/NowPlaying.tsx b/src/components/NowPlaying.tsx index 60d4154..e1a079e 100644 --- a/src/components/NowPlaying.tsx +++ b/src/components/NowPlaying.tsx @@ -8,6 +8,7 @@ import { SelectedMetrics, SongMetricData } from "../types/enhancify"; import { allMetrics, getSongMetrics } from "../services/enhancifyInternalService"; import RecommendationsModal from "./RecommendationsModal"; import SettingsModal from "./SettingsModal"; +import ListeningHistoryModal from "./ListeningHistoryModal"; import Modal from 'react-modal'; class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesResponse | {}, @@ -17,6 +18,7 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon metricsToDisplay: string[], modalIsOpen: boolean, settingsModalIsOpen: boolean, + historyModalIsOpen: boolean, selectedMetrics: SelectedMetrics}> { state = { @@ -29,7 +31,8 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon ["Danceability", "Energy", "Acousticness", "Loudness", "Key", "Tempo"] : [], // Current metric information types modalIsOpen: false, // Whether the modal is currently open - settingsModalIsOpen: false, + settingsModalIsOpen: false, + historyModalIsOpen: false, selectedMetrics: JSON.parse(Spicetify.LocalStorage.get("selectedMetrics") || "{}"), // Metrics that have been selected to be fed into the Spotify recommendations endpoint } @@ -120,6 +123,12 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon }); } + setHistoryModalIsOpen = (value: boolean) => { + this.setState({ + historyModalIsOpen: value, + }) + } + // Select a metric to toggle whether they should be included in the recommendations endpoint request or not selectMetric = (metric: string, value: string) => { let copy: SelectedMetrics = { ...this.state.selectedMetrics }; @@ -163,6 +172,20 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon }, } + historicalMetricsModalStyles = { + overlay: { + backgroundColor: "rgba(0, 0, 0, 0.70)", + }, + content: { + position: 'absolute', + top: '43%', + left: '47%', + transform: 'translate(-50%, -50%)', + width: "70%", + height: "640px" + }, + } + render() { Spicetify.Player.addEventListener("songchange", this.setAudioFeatures); @@ -248,13 +271,20 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon marginTop: "auto", marginBottom: "auto"}} /> -
this.setSettingsModalIsOpen(true)}> +
this.setSettingsModalIsOpen(true)}>
+
this.setHistoryModalIsOpen(true)}> + +
@@ -280,6 +310,9 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon + this.setHistoryModalIsOpen(false)} style={this.historicalMetricsModalStyles}> + + ); } diff --git a/src/components/SongMetric.tsx b/src/components/SongMetric.tsx index 15356a9..90afddf 100644 --- a/src/components/SongMetric.tsx +++ b/src/components/SongMetric.tsx @@ -8,8 +8,8 @@ class SongMetric extends React.Component<{floatValue: string, title: string, progressBar: boolean, label: string, - selectMetric: (metric: string, value: string) => void - isMetricSelected: boolean}, + selectMetric: (metric: string, value: string) => void + isMetricSelected?: boolean}, {}> { render() { diff --git a/src/services/recentlyPlayedService.tsx b/src/services/recentlyPlayedService.tsx index 9b95232..20aae0b 100644 --- a/src/services/recentlyPlayedService.tsx +++ b/src/services/recentlyPlayedService.tsx @@ -1,14 +1,29 @@ -import type { GetRecentlyPlayedTracksResponse, GetRecentlyPlayedTracksInput, AudioFeaturesResponse } from "../types/spotify-web-api"; +import type { AudioFeaturesResponse } from "../types/spotify-web-api"; import getMultiTrackAudioFeatures from "./multiTrackAudioFeaturesService"; import type { HistoricalMetrics } from "../types/enhancify"; -async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTracksInput) : Promise { - let url = "https://api.spotify.com/v1/me/player/recently-played"; +async function getRecentlyPlayedTracksMetrics() : Promise { + + // Set the API option + let apiOptions = { + limit: "50", + after: "", + }; + // Get the date of the first day of the week + let today = new Date(); + let day = today.getDay() || 7; + if (day != 1) { + today.setHours(-24 * (day - 1)); + } + // Convert the date to UNIX format + apiOptions.after = (today.getTime() / 1000).toFixed(0); + + let url = "https://api.spotify.com/v1/me/player/recently-played?"; // Extract the query parameters from the API options input // to create a request string let queryParams: string[] = []; - for (const [key, value] of Object.entries(apiOptions.data)) { + for (const [key, value] of Object.entries(apiOptions)) { if (!value) { continue; } @@ -25,7 +40,6 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack headers: { Authorization: "Bearer " + accessToken, }, - } ); @@ -43,7 +57,6 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack songIDs.push(item.track.id); } let audioFeatures = await getMultiTrackAudioFeatures(songIDs); - // Process the audio features let relevantMetrics = ["acousticness", "danceability", "energy", "instrumentalness", "liveness", "speechiness", "valence"]; @@ -53,9 +66,9 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack } for (let audioFeature of audioFeatures) { for (let feature of Object.keys(audioFeature)) { - if (feature in relevantMetrics) { + if (relevantMetrics.includes(feature)) { if (audioFeature[feature as keyof AudioFeaturesResponse]) { - metricsData[feature].total += parseInt(audioFeature[feature as keyof AudioFeaturesResponse]); + metricsData[feature].total += parseFloat(audioFeature[feature as keyof AudioFeaturesResponse]); metricsData[feature].count += 1; } } @@ -93,10 +106,12 @@ async function getRecentlyPlayedTracksMetrics(apiOptions: GetRecentlyPlayedTrack count: 0 } }; - for (let metric in Object.keys(metricsData)) { + for (let metric of Object.keys(metricsData)) { result[metric as keyof HistoricalMetrics].average = metricsData[metric].total / metricsData[metric].count; result[metric as keyof HistoricalMetrics].count = metricsData[metric].count; } return result; -} \ No newline at end of file +} + +export default getRecentlyPlayedTracksMetrics; \ No newline at end of file