diff --git a/package-lock.json b/package-lock.json index 5bebc9b..938db6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,13 @@ "@spotify/web-api-ts-sdk": "^1.2.0", "@types/jquery": "^3.5.31", "jquery": "^3.7.1", - "react-circular-progressbar": "^2.1.0" + "react-circular-progressbar": "^2.1.0", + "react-modal": "^3.16.1" }, "devDependencies": { "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", + "@types/react-modal": "^3.16.3", "spicetify-creator": "^1.0.17" } }, @@ -81,6 +83,15 @@ "@types/react": "*" } }, + "node_modules/@types/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/sizzle": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", @@ -802,6 +813,11 @@ "node": ">=6" } }, + "node_modules/exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "node_modules/expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -1214,8 +1230,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/jsonfile": { "version": "6.1.0", @@ -1299,7 +1314,6 @@ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -1420,6 +1434,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1624,6 +1646,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "node_modules/prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -1653,6 +1685,47 @@ "react": "^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "node_modules/react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "dependencies": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + }, + "engines": { + "node": ">=8" + }, + "peerDependencies": { + "react": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18", + "react-dom": "^0.14.0 || ^15.0.0 || ^16 || ^17 || ^18" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -1756,6 +1829,15 @@ "dev": true, "optional": true }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, "node_modules/semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -1978,6 +2060,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -2049,6 +2139,15 @@ "@types/react": "*" } }, + "@types/react-modal": { + "version": "3.16.3", + "resolved": "https://registry.npmjs.org/@types/react-modal/-/react-modal-3.16.3.tgz", + "integrity": "sha512-xXuGavyEGaFQDgBv4UVm8/ZsG+qxeQ7f77yNrW3n+1J6XAstUy5rYHeIHPh1KzsGc6IkCIdu6lQ2xWzu1jBTLg==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/sizzle": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz", @@ -2452,6 +2551,11 @@ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", "dev": true }, + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + }, "expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -2760,8 +2864,7 @@ "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "peer": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "jsonfile": { "version": "6.1.0", @@ -2825,7 +2928,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "peer": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -2904,6 +3006,11 @@ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3041,6 +3148,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", @@ -3063,6 +3180,37 @@ "integrity": "sha512-xp4THTrod4aLpGy68FX/k1Q3nzrfHUjUe5v6FsdwXBl3YVMwgeXYQKDrku7n/D6qsJA9CuunarAboC2xCiKs1g==", "requires": {} }, + "react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "peer": true, + "requires": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-modal": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.16.1.tgz", + "integrity": "sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg==", + "requires": { + "exenv": "^1.2.0", + "prop-types": "^15.7.2", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + } + }, "readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -3144,6 +3292,15 @@ "dev": true, "optional": true }, + "scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "peer": true, + "requires": { + "loose-envify": "^1.1.0" + } + }, "semver": { "version": "5.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", @@ -3294,6 +3451,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 2eb0d3b..c169767 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,14 @@ "devDependencies": { "@types/react": "^18.3.4", "@types/react-dom": "^18.3.0", + "@types/react-modal": "^3.16.3", "spicetify-creator": "^1.0.17" }, "dependencies": { "@spotify/web-api-ts-sdk": "^1.2.0", "@types/jquery": "^3.5.31", "jquery": "^3.7.1", - "react-circular-progressbar": "^2.1.0" + "react-circular-progressbar": "^2.1.0", + "react-modal": "^3.16.1" } } diff --git a/src/components/DynamicRecommendations.tsx b/src/components/DynamicRecommendations.tsx index 4b87cc6..8b4c829 100644 --- a/src/components/DynamicRecommendations.tsx +++ b/src/components/DynamicRecommendations.tsx @@ -4,6 +4,7 @@ import getRecommendations from "../services/dynamicRecommendationsService"; import { GetRecommendationsInput, GetRecommendationsResponse } from "../types/spotify-web-api.d"; import getID from './../services/common'; import RecommendedTrack from "./RecommendedTrack"; +import { RecommendationsRender } from "../services/enhancifyInternalService"; class DynamicRecommendations extends React.Component<{recTargetProp : string}, {songQueue: Array, artistQueue: Array, recTarget: string, recommendations: GetRecommendationsResponse | {}}> { @@ -153,24 +154,7 @@ class DynamicRecommendations extends React.Component<{recTargetProp : string}, {
{this.props.recTargetProp}
- {function(recommendations : GetRecommendationsResponse | {}) { - if (Object.keys(recommendations).length == 0) { - return; - } - let recs = (recommendations as GetRecommendationsResponse)["tracks"]; - let recommendedTracksHTML = []; - for (let i = 0; i < 6; i++) { - let recommendedSong = artist.name)} - songURI={recs[i].uri} - key={i}> - ; - recommendedTracksHTML.push(recommendedSong); - } - return recommendedTracksHTML; - }(this.state.recommendations)} + {RecommendationsRender(this.state.recommendations)}
diff --git a/src/components/NowPlaying.tsx b/src/components/NowPlaying.tsx index 7357ebf..f2e42ca 100644 --- a/src/components/NowPlaying.tsx +++ b/src/components/NowPlaying.tsx @@ -4,14 +4,18 @@ import getAudioFeatures from "../services/nowPlayingService"; import { AudioFeaturesResponse } from "../types/spotify-web-api"; import DynamicRecommendations from "./DynamicRecommendations"; import SongMetric from "./SongMetric"; -import { SongMetricData } from "../types/enhancify"; +import { SelectedMetrics, SongMetricData } from "../types/enhancify"; import { allMetrics, getSongMetrics } from "../services/enhancifyInternalService"; +import RecommendationsModal from "./RecommendationsModal"; +import Modal from 'react-modal'; class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesResponse | {}, songURI: string, recTarget: string, songMetrics: SongMetricData[], - metricsToDisplay: string[]}> { + metricsToDisplay: string[], + modalIsOpen: boolean, + selectedMetrics: SelectedMetrics}> { state = { audioFeatures: {}, // Features of the currently playing song (name, artist, stats) @@ -19,6 +23,8 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon recTarget: "songs", // Recommendations based on either songs or artist songMetrics: [], // Current song metric information metricsToDisplay: Spicetify.LocalStorage.get("metricsToDisplay") != "" ? Spicetify.LocalStorage.get("metricsToDisplay")?.split(',') || ["Danceability", "Energy", "Acousticness", "Loudness", "Key", "Tempo"] : [], // Current metric information types + modalIsOpen: false, // Whether the modal is currently open + selectedMetrics: JSON.parse(Spicetify.LocalStorage.get("selectedMetrics") || "{}"), // Metrics that have been selected to be fed into the Spotify recommendations endpoint } componentDidMount = () => { @@ -26,7 +32,6 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon } setAudioFeatures = () => { - // Check if there is no currently playing song or // if the info of the song is currently being displayed if (!Spicetify.Player.data || this.state.songURI == Spicetify.Player.data.item.uri) { @@ -73,6 +78,16 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon let newArray = this.state.metricsToDisplay.slice(); if (newArray.includes(metric)) { newArray = newArray.filter((val) => val != metric); + + // If a metric is being hidden from the display, it should not be fed into the recommendations endpoint + if (metric in this.state.selectedMetrics) { + let copy: SelectedMetrics = { ...this.state.selectedMetrics }; + delete copy[metric]; + Spicetify.LocalStorage.set("selectedMetrics", JSON.stringify(copy)); + this.setState({ + selectedMetrics: copy + }); + } } else { newArray.push(metric); @@ -85,6 +100,28 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon }, this.setSongMetrics); } + // Set whether the modal should be open or closed + setModalIsOpen = (value: boolean) => { + this.setState({ + modalIsOpen: 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 }; + if (metric in copy) { + delete copy[metric]; + } + else { + copy[metric] = value; + } + Spicetify.LocalStorage.set("selectedMetrics", JSON.stringify(copy)); + this.setState({ + selectedMetrics: copy + }); + } + render() { Spicetify.Player.addEventListener("songchange", this.setAudioFeatures); @@ -164,11 +201,14 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon
{"Song Statistics"}
+
{/* Stats block data */} {this.state.songMetrics.map((songMetric: SongMetricData, i) => { - return ; + return ; })}
@@ -192,6 +232,9 @@ class NowPlaying extends React.Component<{}, {audioFeatures: AudioFeaturesRespon })} + this.setModalIsOpen(false)}> + + ); } diff --git a/src/components/RecommendationsModal.tsx b/src/components/RecommendationsModal.tsx new file mode 100644 index 0000000..907230c --- /dev/null +++ b/src/components/RecommendationsModal.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { GetRecommendationsInput, GetRecommendationsResponse, RecommendationsInput } from "../types/spotify-web-api.d"; +import getRecommendations from "../services/dynamicRecommendationsService"; +import { SelectedMetrics } from "../types/enhancify"; +import getID from './../services/common'; +import { RecommendationsRender } from "../services/enhancifyInternalService"; + +class RecommendationsModal extends React.Component<{setModalIsOpen: (value: boolean) => void, songURI: string, selectedMetrics: SelectedMetrics}, {recommendations: GetRecommendationsResponse | {}}> { + + state = { + recommendations: {} // Recommendations that show up in the modal view + }; + + componentDidMount = () => { + this.generateRecommendations(); + } + + // Generate recommendations by sending a request to the spotify API + generateRecommendations = async () => { + + // Prepare the recommendations to send to the server + let apiOptions = new GetRecommendationsInput(); + apiOptions.data.seed_tracks = getID(this.props.songURI); + apiOptions.data.limit = "10"; + + for (let key in this.props.selectedMetrics) { + let apiDataKey = "target_" + key.toLowerCase(); + apiOptions.data[apiDataKey as keyof RecommendationsInput] = this.props.selectedMetrics[key]; + } + + // Make the API call + var recommendations = await getRecommendations(apiOptions); + this.setState({ + recommendations: recommendations, + }); + }; + + render() { + return ( + <> + + {RecommendationsRender(this.state.recommendations)} + + ); + } +} + +export default RecommendationsModal; diff --git a/src/components/SongMetric.tsx b/src/components/SongMetric.tsx index 09e6c5a..1d58b2b 100644 --- a/src/components/SongMetric.tsx +++ b/src/components/SongMetric.tsx @@ -7,12 +7,14 @@ import 'react-circular-progressbar/dist/styles.css'; class SongMetric extends React.Component<{floatValue: string, title: string, progressBar: boolean, - label: string}, + label: string, + selectMetric: (metric: string, value: string) => void + isMetricSelected: boolean}, {}> { render() { return ( -
+
this.props.selectMetric(this.props.title, this.props.floatValue)} style={this.props.isMetricSelected ? {borderWidth: "5px", borderStyle: "solid", borderColor: "white"} : {}}>
{this.props.title} diff --git a/src/css/app.module.scss b/src/css/app.module.scss index 03cb32e..52f09f8 100644 --- a/src/css/app.module.scss +++ b/src/css/app.module.scss @@ -245,4 +245,15 @@ font-size: larger; font-weight: 500; color: white; +} + +.modal { + background-color: black; + height: 80%; + width: 80%; + margin-right: 50px; + margin-top: 50px; + margin-left: 50px; + margin-bottom: 50px; + text-wrap: wrap; } \ No newline at end of file diff --git a/src/services/enhancifyInternalService.tsx b/src/services/enhancifyInternalService.tsx index 16b3e70..5a0f1c6 100644 --- a/src/services/enhancifyInternalService.tsx +++ b/src/services/enhancifyInternalService.tsx @@ -1,5 +1,27 @@ +import React from "react"; +import RecommendedTrack from "../components/RecommendedTrack"; import { Labels, MetricFeatures, SongMetricData } from "../types/enhancify"; -import { AudioFeaturesResponse } from "../types/spotify-web-api"; +import { AudioFeaturesResponse, GetRecommendationsResponse } from "../types/spotify-web-api"; + +// Creates the recommended track view for any response from the Spotify recommendations endpoint +export function RecommendationsRender(recommendations : GetRecommendationsResponse | {}) { + if (Object.keys(recommendations).length == 0) { + return; + } + let recs = (recommendations as GetRecommendationsResponse)["tracks"]; + let recommendedTracksHTML = []; + for (let i = 0; i < recs.length; i++) { + let recommendedSong = artist.name)} + songURI={recs[i].uri} + key={i}> + ; + recommendedTracksHTML.push(recommendedSong); + } + return recommendedTracksHTML; +} // Dynamically fills in the song metric information based on the specific metrics that the user wants to display export function getSongMetrics(audioFeatures: AudioFeaturesResponse, metricsToDisplay: string[]): SongMetricData[] { diff --git a/src/types/enhancify.d.ts b/src/types/enhancify.d.ts index 2ab7859..3df84f1 100644 --- a/src/types/enhancify.d.ts +++ b/src/types/enhancify.d.ts @@ -17,3 +17,7 @@ export type MetricFeatures = { progressbar: Set, label: Labels }; + +export type SelectedMetrics = { + [metric: string]: string +}; diff --git a/src/types/spotify-web-api.d.ts b/src/types/spotify-web-api.d.ts index eb5971e..4e33947 100644 --- a/src/types/spotify-web-api.d.ts +++ b/src/types/spotify-web-api.d.ts @@ -20,55 +20,57 @@ export type AudioFeaturesResponse = { }; export class GetRecommendationsInput { - data = { - limit: "6", - market: "", - seed_artists: "", - seed_genres: "", - seed_tracks: "", - min_acousticness: "", - max_acousticness: "", - target_acousticness: "", - min_danceability: "", - max_danceability: "", - target_danceability: "", - min_duration_ms: "", - max_duration_ms: "", - target_duration_ms: "", - min_energy: "", - max_energy: "", - target_energy: "", - min_instrumentalness: "", - max_instrumentalness: "", - target_instrumentalness: "", - min_key: "", - max_key: "", - target_key: "", - min_liveness: "", - max_liveness: "", - target_liveness: "", - min_loudness: "", - max_loudness: "", - target_loudness: "", - min_mode: "", - max_mode: "", - target_mode: "", - min_popularity: "", - max_popularity: "", - target_popularity: "", - min_speechiness: "", - max_speechiness: "", - target_speechiness: "", - min_tempo: "", - max_tempo: "", - target_tempo: "", - min_time_signature: "", - max_time_signature: "", - target_time_signature: "", - min_valence: "", - max_valence: "", - target_valence: "", - }; + data = new RecommendationsInput(); +}; + +export class RecommendationsInput { + limit = "6"; + market = ""; + seed_artists = ""; + seed_genres = ""; + seed_tracks = ""; + min_acousticness = ""; + max_acousticness = ""; + target_acousticness = ""; + min_danceability = ""; + max_danceability = ""; + target_danceability = ""; + min_duration_ms = ""; + max_duration_ms = ""; + target_duration_ms = ""; + min_energy = ""; + max_energy = ""; + target_energy = ""; + min_instrumentalness = ""; + max_instrumentalness = ""; + target_instrumentalness = ""; + min_key = ""; + max_key = ""; + target_key = ""; + min_liveness = ""; + max_liveness = ""; + target_liveness = ""; + min_loudness = ""; + max_loudness = ""; + target_loudness = ""; + min_mode = ""; + max_mode = ""; + target_mode = ""; + min_popularity = ""; + max_popularity = ""; + target_popularity = ""; + min_speechiness = ""; + max_speechiness = ""; + target_speechiness = ""; + min_tempo = ""; + max_tempo = ""; + target_tempo = ""; + min_time_signature = ""; + max_time_signature = ""; + target_time_signature = ""; + min_valence = ""; + max_valence = ""; + target_valence = ""; }; export type GetRecommendationsResponse = {