Delete old files
This commit is contained in:
parent
510e4aeeb0
commit
aca049f778
16 changed files with 0 additions and 744 deletions
84
app.js
84
app.js
|
|
@ -1,84 +0,0 @@
|
||||||
var vue_config = {
|
|
||||||
el: `#app`,
|
|
||||||
data: {
|
|
||||||
api_base: `https://api.spotify.com/v1`,
|
|
||||||
duration: ``,
|
|
||||||
type: ``,
|
|
||||||
count: ``,
|
|
||||||
show: {
|
|
||||||
popularity_popup: false,
|
|
||||||
popularity_hover: false,
|
|
||||||
follower_hover: false,
|
|
||||||
modal_content: false,
|
|
||||||
modal: {
|
|
||||||
popularity: false,
|
|
||||||
track: false,
|
|
||||||
artist: false,
|
|
||||||
playlist_export: false,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: {
|
|
||||||
main: ``,
|
|
||||||
auth: ``,
|
|
||||||
},
|
|
||||||
auth: {
|
|
||||||
alert: `We will only be able to access your top tracks and artists, nothing else. This is also only done on your browser. Our servers do not see any of the data from your account.`,
|
|
||||||
base_url: `https://accounts.spotify.com/authorize`,
|
|
||||||
redirect: `http://oliver.akins.me/top-lists`,
|
|
||||||
client_id: `3a1795e9d55445b0aa0c05dd74c866fb`,
|
|
||||||
scopes: [
|
|
||||||
`user-top-read`
|
|
||||||
],
|
|
||||||
show_dialog: false,
|
|
||||||
use_state: false
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
name: ``,
|
|
||||||
image: ``
|
|
||||||
},
|
|
||||||
data: {
|
|
||||||
tracks: [],
|
|
||||||
artists: []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
spotify_auth_url: auth_url,
|
|
||||||
is_authed: verify_auth,
|
|
||||||
button_type: get_button_text,
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
get_token: function () {
|
|
||||||
let params = new URLSearchParams(window.location.hash.slice(1));
|
|
||||||
return params.get(`access_token`);
|
|
||||||
},
|
|
||||||
get_user: function () {
|
|
||||||
axios.get(
|
|
||||||
`${this.api_base}/me`,
|
|
||||||
{ headers: { Authorization: `Bearer ${this.get_token()}` } }
|
|
||||||
).then((response) => {
|
|
||||||
if (response.error) {
|
|
||||||
window.location.hash = ``;
|
|
||||||
window.location.href = this.auth.redirect;
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let data = response.data;
|
|
||||||
|
|
||||||
// Set the Vue user object
|
|
||||||
this.user.name = data.display_name;
|
|
||||||
this.user.image = data.images.length > 0 ? data.images[0].url : ``;
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
window.location.hash = ``;
|
|
||||||
window.location.href = this.auth.redirect;
|
|
||||||
return
|
|
||||||
})
|
|
||||||
},
|
|
||||||
get_data: fetch_data,
|
|
||||||
hide_modal: function () {
|
|
||||||
this.show.modal.popularity = false;
|
|
||||||
this.show.modal.track = false;
|
|
||||||
this.show.modal.artist = false;
|
|
||||||
this.show.modal.playlist_export = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<div class="card">
|
|
||||||
<div class="image">
|
|
||||||
<img v-if="artist.image" :src="artist.image.url" :alt="artist.name + 's profile picture'" target="_blank" rel="noopener">
|
|
||||||
<div class="missing-image" v-else>
|
|
||||||
<music-note colour="#1DB954"></music-note>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<span class="title">
|
|
||||||
<a :href="artist.link" target="_blank" rel="noopener">{{artist.name}}</a>
|
|
||||||
</span>
|
|
||||||
<br>
|
|
||||||
<span class="subtitle">{{genres}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="bottom left corner popularity" v-tooltip="popularity_tooltip" @click.self="show_popularity_modal()">{{artist.popularity}}</div>
|
|
||||||
<div class="bottom right corner followers" v-tooltip="followers_tooltip">{{artist.follower_count.toLocaleString()}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
Vue.component(
|
|
||||||
`artist`,
|
|
||||||
{
|
|
||||||
props: [ `artist` ],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
popularity_tooltip: `Popularity`,
|
|
||||||
followers_tooltip: `Followers`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
genres: function () {
|
|
||||||
let genres = [];
|
|
||||||
for (var genre of this.artist.genres) {
|
|
||||||
genres.push(genre.toTitleCase());
|
|
||||||
};
|
|
||||||
return genres.join(`, `);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
show_popularity_modal: function () {
|
|
||||||
this.$emit(`popularity_click`)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `<div class="card">
|
|
||||||
<div class="image">
|
|
||||||
<img v-if="artist.image" :src="artist.image.url" :alt="artist.name + 's profile picture'" target="_blank" rel="noopener">
|
|
||||||
<div class="missing-image" v-else>
|
|
||||||
<music-note colour="#1DB954"></music-note>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<span class="title">
|
|
||||||
<a :href="artist.link" target="_blank" rel="noopener">{{artist.name}}</a>
|
|
||||||
</span>
|
|
||||||
<br>
|
|
||||||
<span class="subtitle">{{genres}}</span>
|
|
||||||
</div>
|
|
||||||
<div class="bottom left corner popularity" v-tooltip="popularity_tooltip" @click.self="show_popularity_modal()">{{artist.popularity}}</div>
|
|
||||||
<div class="bottom right corner followers" v-tooltip="followers_tooltip">{{artist.follower_count.toLocaleString()}}</div>
|
|
||||||
</div>`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
Vue.component(
|
|
||||||
`music-note`,
|
|
||||||
{
|
|
||||||
props: [ `colour` ],
|
|
||||||
template: `<svg width="72" height="72" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="24" fill="none" rx="0" ry="0"></rect><path fill-rule="evenodd" clip-rule="evenodd" d="M17.4911 2.11667L21.4511 4.09667C21.9411 4.34667 22.1411 4.93667 21.8911 5.43667C21.6411 5.93667 21.0411 6.13667 20.5511 5.88667L18.0011 4.61667L17.9911 16.4367C17.9911 16.4467 17.9936 16.4567 17.9961 16.4667C17.9986 16.4767 18.0011 16.4867 18.0011 16.4967C18.0011 18.4267 16.4311 19.9967 14.5011 19.9967C12.5711 19.9967 11.0011 18.4267 11.0011 16.4967C11.0011 14.5667 12.5711 12.9967 14.5011 12.9967C15.0411 12.9967 15.5411 13.1267 16.0011 13.3467L16.0111 3.10667C15.9911 2.99667 16.0011 2.87667 16.0311 2.75667C16.1711 2.14667 16.9411 1.80667 17.4911 2.11667ZM12.8911 5.43664C12.6511 5.93664 12.0511 6.13664 11.5511 5.88664L9.0011 4.61664L8.9911 18.4366C8.9911 18.4466 8.9936 18.4566 8.9961 18.4666C8.9986 18.4766 9.0011 18.4866 9.0011 18.4966C9.0011 20.4266 7.4311 21.9966 5.5011 21.9966C3.5711 21.9966 2.0011 20.4266 2.0011 18.4966C2.0011 16.5666 3.5711 14.9966 5.5011 14.9966C6.0411 14.9966 6.5411 15.1266 7.0011 15.3466L7.0111 3.10664C6.9911 2.99664 7.0011 2.87664 7.0311 2.76664C7.1711 2.14664 7.9411 1.81664 8.4911 2.12664L12.4411 4.09664C12.9411 4.34664 13.1411 4.94664 12.8911 5.43664Z" :fill="colour || '#ffffff'"></path></svg>`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
Vue.component(
|
|
||||||
`close`,
|
|
||||||
{
|
|
||||||
props: [ `colour` ],
|
|
||||||
template: `<svg width="72" height="72" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect width="24" height="24" fill="none" rx="0" ry="0"></rect><path fill-rule="evenodd" clip-rule="evenodd" d="M13.4049 12.0025L18.6949 6.7125C19.0849 6.3225 19.0849 5.6925 18.6949 5.3025C18.3049 4.9125 17.6749 4.9125 17.2849 5.3025L11.9949 10.5925L6.70494 5.2925C6.31494 4.9025 5.68494 4.9025 5.29494 5.2925C4.90494 5.6825 4.90494 6.3125 5.29494 6.7025L10.5849 12.0025L5.29494 17.2925C4.90494 17.6825 4.90494 18.3125 5.29494 18.7025C5.48494 18.9025 5.73494 19.0025 5.99494 19.0025C6.25494 19.0025 6.50494 18.9025 6.70494 18.7125L11.9949 13.4125L17.2849 18.7025C17.4849 18.9025 17.7349 19.0025 17.9949 19.0025C18.2549 19.0025 18.5049 18.9025 18.7049 18.7125C19.0949 18.3225 19.0949 17.6925 18.7049 17.3025L13.4049 12.0025Z" :fill="colour || '#ffffff'"></path></svg>`
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
<div class="card">
|
|
||||||
<div class="image">
|
|
||||||
<a v-if="track.album.image.url" :href="track.album.link" target="_blank" rel="noopener">
|
|
||||||
<img :src="track.album.image.url" :alt="track.album.name + ' Cover Image'" class="cover">
|
|
||||||
</a>
|
|
||||||
<a v-else :href="track.album.link" target="_blank" rel="noopener">
|
|
||||||
<div class="missing-image">
|
|
||||||
<music-note colour="#1DB954"></music-note>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<span class="title remote" v-if="!track.locality">
|
|
||||||
<a :href="track.link" target="_blank" rel="noopener">{{track.name}}</a>
|
|
||||||
</span>
|
|
||||||
<span class="title local" v-else>{{track.name}}</span>
|
|
||||||
<br>
|
|
||||||
<span class="subtitle" v-html="artists"></span>
|
|
||||||
</div>
|
|
||||||
<div class="popularity bottom left corner" v-tooltip="popularity_tooltip" @click.self="show_popularity_modal()">{{track.popularity}}</div>
|
|
||||||
<div class="duration bottom right corner">{{duration}}</div>
|
|
||||||
</div>
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
Vue.component(
|
|
||||||
`track-card`,
|
|
||||||
{
|
|
||||||
props: [ `track` ],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
popularity_tooltip: `Popularity`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
duration: function () {
|
|
||||||
let timestamp = ``;
|
|
||||||
|
|
||||||
// Converting to seconds
|
|
||||||
let duration = Math.trunc(this.track.duration / 1000);
|
|
||||||
let seconds = duration % 60;
|
|
||||||
|
|
||||||
// Converting to minutes
|
|
||||||
duration = Math.trunc(duration / 60);
|
|
||||||
let minutes = duration % 60
|
|
||||||
|
|
||||||
// Converting to hours
|
|
||||||
duration = Math.trunc(duration / 60);
|
|
||||||
let hours = duration % 24;
|
|
||||||
|
|
||||||
if (seconds < 10) {
|
|
||||||
seconds = `0${seconds}`
|
|
||||||
};
|
|
||||||
|
|
||||||
return `${hours > 0 ? `${hours}:` : ''}${minutes}:${seconds}`;
|
|
||||||
},
|
|
||||||
artists: function () {
|
|
||||||
let artists = [];
|
|
||||||
for (var artist of this.track.artists) {
|
|
||||||
artists.push(
|
|
||||||
`<a href="${artist.external_urls.spotify}" target="_blank" rel="noopener">${artist.name}</a>`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return artists.join(`, `)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
show_popularity_modal: function () {
|
|
||||||
this.$emit('popularity_click')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
template: `<div class="card">
|
|
||||||
<div class="image">
|
|
||||||
<a v-if="track.album.image.url" :href="track.album.link" target="_blank" rel="noopener">
|
|
||||||
<img :src="track.album.image.url" :alt="track.album.name + ' Cover Image'" class="cover">
|
|
||||||
</a>
|
|
||||||
<a v-else :href="track.album.link" target="_blank" rel="noopener">
|
|
||||||
<div class="missing-image">
|
|
||||||
<music-note colour="#1DB954"></music-note>
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
<div class="info">
|
|
||||||
<span class="title remote" v-if="!track.locality">
|
|
||||||
<a :href="track.link" target="_blank" rel="noopener">{{track.name}}</a>
|
|
||||||
</span>
|
|
||||||
<span class="title local" v-else>{{track.name}}</span>
|
|
||||||
<br>
|
|
||||||
<span class="subtitle" v-html="artists"></span>
|
|
||||||
</div>
|
|
||||||
<div class="popularity bottom left corner" v-tooltip="popularity_tooltip" @click.self="show_popularity_modal()">{{track.popularity}}</div>
|
|
||||||
<div class="duration bottom right corner">{{duration}}</div>
|
|
||||||
</div>`
|
|
||||||
}
|
|
||||||
);
|
|
||||||
87
css/card.css
87
css/card.css
|
|
@ -1,87 +0,0 @@
|
||||||
div.card {
|
|
||||||
/* Card only variables */
|
|
||||||
--border-radius: 5px;
|
|
||||||
|
|
||||||
background-color: var(--card-colour);
|
|
||||||
color: var(--card-text);
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
border-style: none;
|
|
||||||
padding: 10px;
|
|
||||||
padding-top: 20px;
|
|
||||||
margin: 5px auto;
|
|
||||||
width: 90%;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.card > div.image {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
div.card > div.image img {
|
|
||||||
--size: 200px;
|
|
||||||
width: var(--size);
|
|
||||||
height: var(--size);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.card div.missing-image {
|
|
||||||
background-color: #3a3a3aaa;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 100px;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
|
||||||
height: 200px;
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.card > div.info {
|
|
||||||
text-align: center;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
div.card > div.info > .title { font-size: larger; }
|
|
||||||
div.card > div.info > .subtitle { font-size: smaller; }
|
|
||||||
|
|
||||||
/* Positioning For Absolute Elements */
|
|
||||||
div.card .corner {
|
|
||||||
position: absolute;
|
|
||||||
padding: 1px 6px;
|
|
||||||
background-color: var(--on-card-colour);
|
|
||||||
color: var(--on-card-text);
|
|
||||||
}
|
|
||||||
div.card .right { right: 0; }
|
|
||||||
div.card .left { left: 0; }
|
|
||||||
div.card .top { top: 0; }
|
|
||||||
div.card .bottom { bottom: 0; }
|
|
||||||
|
|
||||||
|
|
||||||
/* border-radius: top-left top-right lower-right lower-left */
|
|
||||||
div.card .popularity {
|
|
||||||
border-radius: 0 var(--border-radius) 0 var(--border-radius);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
div.card .followers,
|
|
||||||
div.card .duration { border-radius: var(--border-radius) 0 var(--border-radius) 0; }
|
|
||||||
|
|
||||||
|
|
||||||
div.card a {
|
|
||||||
color: var(--text-on-card);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
div.card a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* DESKTOP STYLES */
|
|
||||||
@media only screen and (min-width: 768px) {
|
|
||||||
div.card {
|
|
||||||
width: 230px;
|
|
||||||
margin: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
div.modal-container {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
z-index: 5;
|
|
||||||
background-color: var(--modal-container-background);
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.modal {
|
|
||||||
z-index: 6;
|
|
||||||
width: 90%;
|
|
||||||
max-height: 85%;
|
|
||||||
background-color: var(--modal-background);
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 0 15px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.modal * { color: var(--modal-text); }
|
|
||||||
|
|
||||||
div.modal h2 { margin-top: 30px; }
|
|
||||||
|
|
||||||
div.modal p { padding: 0px 5px 15px 5px; }
|
|
||||||
|
|
||||||
div.modal .close-modal {
|
|
||||||
width: 25px;
|
|
||||||
height: 25px;
|
|
||||||
position: absolute;
|
|
||||||
right: 10px;
|
|
||||||
top: 10px;
|
|
||||||
color: var(--spotify-green);
|
|
||||||
}
|
|
||||||
div.modal .close-modal:hover {
|
|
||||||
color: var(--error);
|
|
||||||
transform: scale(1.2, 1.2);
|
|
||||||
transition: ease-in-out;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (min-width: 768px) {
|
|
||||||
div.modal {
|
|
||||||
width: 50%;
|
|
||||||
max-height: 75%;
|
|
||||||
}
|
|
||||||
div.modal h2 {
|
|
||||||
margin-top: 19.920px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
/* Transition for modal background appearing/disappearing */
|
|
||||||
.fade-enter-active, .fade-leave-active {
|
|
||||||
transition: opacity .5s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.fade-enter, .fade-leave-to {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Transition for modal card appearing disappearing */
|
|
||||||
.burst-enter-active {
|
|
||||||
animation: burst-in .5s;
|
|
||||||
}
|
|
||||||
.burst-leave-active { animation: burst-out .5s; }
|
|
||||||
|
|
||||||
@keyframes burst-in {
|
|
||||||
0% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes burst-out {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media only screen and (min-width: 768px) {
|
|
||||||
@keyframes burst-in {
|
|
||||||
0% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.25);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes burst-out {
|
|
||||||
0% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
50% {
|
|
||||||
transform: scale(1.25);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
119
index.html
119
index.html
|
|
@ -1,119 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Top Lists For Spotify</title>
|
|
||||||
|
|
||||||
<!-- Stylesheets -->
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:wght@800&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="style.css">
|
|
||||||
<link rel="stylesheet" href="./css/card.css">
|
|
||||||
<link rel="stylesheet" href="./css/modal.css">
|
|
||||||
<link rel="stylesheet" href="./css/transitions.css">
|
|
||||||
|
|
||||||
<!-- Javascript Imports -->
|
|
||||||
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
|
|
||||||
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
|
|
||||||
<script src="./components/artist.js" defer async></script>
|
|
||||||
<script src="./components/track.js" defer async></script>
|
|
||||||
<script src="./components/icons.js" defer async></script>
|
|
||||||
<script src="https://unpkg.com/v-tooltip"></script>
|
|
||||||
<script src="./js/text_computation.js"></script>
|
|
||||||
<script src="./js/prototypes.js" async></script>
|
|
||||||
<script src="./js/dev/data.js" async></script>
|
|
||||||
<script src="./js/auth.js"></script>
|
|
||||||
<script src="./js/data.js"></script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="app" v-cloak>
|
|
||||||
<div id="login" v-if="!is_authed">
|
|
||||||
<div class="center">
|
|
||||||
<a :href="spotify_auth_url">
|
|
||||||
<button id="spotify-login">Login With Spotify</button>
|
|
||||||
</a>
|
|
||||||
<p class="error" v-if="error.auth">{{error.auth}}</p>
|
|
||||||
<p>{{auth.alert}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="main" v-else v-cloak>
|
|
||||||
<div class="flex-row">
|
|
||||||
<div v-cloak class="account-info">
|
|
||||||
<img v-if="user.image" :src="user.image" :alt="`${user.name}'s profile picture`" class="profile-picture">
|
|
||||||
{{user.name}}
|
|
||||||
</div>
|
|
||||||
<div class="type">
|
|
||||||
<select v-model="type">
|
|
||||||
<option value="" disabled>Please Select A Type</option>
|
|
||||||
<option value="Tracks">Tracks</option>
|
|
||||||
<option value="Artists">Artists</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="duration">
|
|
||||||
<select v-model="duration">
|
|
||||||
<option value="" disabled>Please Select A Duration</option>
|
|
||||||
<option value="long_term">Several Years</option>
|
|
||||||
<option value="medium_term">~6 Months</option>
|
|
||||||
<option value="short_term">~4 Weeks</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="limit">
|
|
||||||
<input type="number" v-model="count" placeholder="How Many?">
|
|
||||||
</div>
|
|
||||||
<div id="submit-button" v-if="button_type && duration">
|
|
||||||
<button @click="get_data()">Get Top {{count > 1 ? count : `10`}} {{button_type}}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-row error" v-if="error.main">{{error.main}}</div>
|
|
||||||
<div class="body" v-if="data.tracks.length > 0">
|
|
||||||
<track-card
|
|
||||||
v-for="top_track in data.tracks"
|
|
||||||
:track="top_track"
|
|
||||||
:key="top_track.id"
|
|
||||||
@popularity_click="show.modal.popularity = true"
|
|
||||||
></track-card>
|
|
||||||
</div>
|
|
||||||
<div class="body" v-if="data.artists.length > 0">
|
|
||||||
<artist
|
|
||||||
v-for="top_artist in data.artists"
|
|
||||||
:artist="top_artist"
|
|
||||||
:key="top_artist.id"
|
|
||||||
@popularity_click="show.modal.popularity = true"
|
|
||||||
></artist>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Modals Needed -->
|
|
||||||
<transition name="fade" @after-enter="show.modal_content = true">
|
|
||||||
<div
|
|
||||||
v-if="Object.values(this.show.modal).includes(true)"
|
|
||||||
class="modal-container"
|
|
||||||
@click.self="show.modal_content = false"
|
|
||||||
>
|
|
||||||
<transition name="burst" @after-leave="hide_modal">
|
|
||||||
<div v-if="show.modal_content && show.modal.popularity" class="modal">
|
|
||||||
<span @click="show.modal_content = false">
|
|
||||||
<close class="close-modal" colour="#ffffff80" @click="show.modal_content = false"></close>
|
|
||||||
</span>
|
|
||||||
<h2 class="center">How is Popularity Calculated?</h2>
|
|
||||||
<p class="center">
|
|
||||||
Popularity is a value between 0 and 100 that is calculated by Spotify based on how many plays the song/artist has recieved and how recent those plays are.
|
|
||||||
</p>
|
|
||||||
<p class="center">
|
|
||||||
This means that an artist/song that has had 100 plays today will have a higher popularity than a song/artist that has 100 from a month ago.
|
|
||||||
</p>
|
|
||||||
<p class="center">
|
|
||||||
This number is not updated in real time so refreshing the page will not reflect it's absolute accurate value.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<script src="./app.js"></script>
|
|
||||||
<script src="./js/dev/app_modifications.js"></script>
|
|
||||||
<script>
|
|
||||||
let app = new Vue(vue_config)
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
53
js/auth.js
53
js/auth.js
|
|
@ -1,53 +0,0 @@
|
||||||
function auth_url () {
|
|
||||||
let params = [
|
|
||||||
`client_id=${this.auth.client_id}`,
|
|
||||||
`response_type=token`,
|
|
||||||
`redirect_uri=${encodeURIComponent(this.auth.redirect)}`,
|
|
||||||
`scope=${encodeURIComponent(this.auth.scopes.join(" "))}`,
|
|
||||||
`show_dialog=${this.auth.show_dialog}`
|
|
||||||
];
|
|
||||||
|
|
||||||
// Create the state data if we are using it
|
|
||||||
if (this.auth.use_state) {
|
|
||||||
let state = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
|
|
||||||
params.push(`state=${state}`);
|
|
||||||
localStorage.setItem(`top-spotify-state`, state);
|
|
||||||
};
|
|
||||||
|
|
||||||
return `${this.auth.base_url}?${params.join("&")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
function verify_auth () {
|
|
||||||
let params = new URLSearchParams(window.location.hash.slice(1));
|
|
||||||
|
|
||||||
// Check to ensure the authorization was a success
|
|
||||||
if (params.get(`access_token`)) {
|
|
||||||
this.get_user()
|
|
||||||
|
|
||||||
// Check if we compare state
|
|
||||||
if (this.use_state) {
|
|
||||||
|
|
||||||
// Compare given state to localstorage state
|
|
||||||
let LS_state = localStorage.getItem(`top-spotify-state`);
|
|
||||||
if (LS_state = params.get(`state`)) {
|
|
||||||
console.info(`State compare success`)
|
|
||||||
this.authed = true;
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
console.error(`State compare failed`)
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let error = (new URLSearchParams(window.location.search)).get(`error`)
|
|
||||||
|
|
||||||
// Authorization failed, error to the user
|
|
||||||
if (error !== null) {
|
|
||||||
this.error.auth = `Authentication failed or was cancelled, please try again.`;
|
|
||||||
window.location.hash = ``;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
57
js/data.js
57
js/data.js
|
|
@ -1,57 +0,0 @@
|
||||||
function fetch_data () {
|
|
||||||
let url = `${this.api_base}/me/top/${this.type.toLowerCase()}`;
|
|
||||||
|
|
||||||
let limit = parseInt(this.count);
|
|
||||||
if (!limit) { limit = 10; };
|
|
||||||
|
|
||||||
url += `?limit=${limit}&time_range=${this.duration}`;
|
|
||||||
|
|
||||||
axios.get(
|
|
||||||
url,
|
|
||||||
{ headers: { Authorization: `Bearer ${this.get_token()}` } }
|
|
||||||
).then((response) => {
|
|
||||||
this.data.artists = [];
|
|
||||||
this.data.tracks = [];
|
|
||||||
this.error.main = ``;
|
|
||||||
switch (this.type) {
|
|
||||||
case `Tracks`:
|
|
||||||
for (var track of response.data.items) {
|
|
||||||
this.data.tracks.push({
|
|
||||||
name: track.name,
|
|
||||||
popularity: track.popularity,
|
|
||||||
artists: track.artists,
|
|
||||||
link: track.external_urls.spotify,
|
|
||||||
duration: track.duration_ms,
|
|
||||||
locality: track.is_local,
|
|
||||||
id: track.uri,
|
|
||||||
album: {
|
|
||||||
name: track.album.name,
|
|
||||||
image: track.album.images[1],
|
|
||||||
link: track.album.external_urls.spotify
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
case `Artists`:
|
|
||||||
for (var artist of response.data.items) {
|
|
||||||
this.data.artists.push({
|
|
||||||
name: artist.name,
|
|
||||||
id: artist.id,
|
|
||||||
popularity: artist.popularity,
|
|
||||||
follower_count: artist.followers.total,
|
|
||||||
image: artist.images[1],
|
|
||||||
genres: artist.genres,
|
|
||||||
link: artist.external_urls.spotify
|
|
||||||
});
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
this.error.main = `TypeError: ${this.type} is not a supported category`;
|
|
||||||
break
|
|
||||||
};
|
|
||||||
}).catch((err) => {
|
|
||||||
this.error.main = `${err.name}: ${err.message}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
let params = new URLSearchParams(window.location.search);
|
|
||||||
let dev_mode = !(params.get(`dev`) == null || params.get(`dev`) === `false`);
|
|
||||||
let preview = !(params.get(`preview`) == null || params.get(`preview`) === `false`);
|
|
||||||
|
|
||||||
|
|
||||||
function get_dev_data () {
|
|
||||||
switch (this.type) {
|
|
||||||
case `Artists`:
|
|
||||||
this.data.artists = dev_artists;
|
|
||||||
this.data.tracks = [];
|
|
||||||
break;
|
|
||||||
case `Tracks`:
|
|
||||||
this.data.artists = [];
|
|
||||||
this.data.tracks = dev_tracks;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.data.tracks = [];
|
|
||||||
this.data.artists = [];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (dev_mode || preview) {
|
|
||||||
vue_config.data.user.name = `Preview Mode`;
|
|
||||||
vue_config.methods.get_data = get_dev_data;
|
|
||||||
vue_config.computed.is_authed = function () { return true; };
|
|
||||||
};
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
const dev_tracks = [{name:"Into the Unknown",popularity:34,locality:false,link:"https://open.spotify.com/track/37POsvF7xupvgDnYYIXoYX",id:"spotify:track:37POsvF7xupvgDnYYIXoYX",duration:225273,artists:[{external_urls:{spotify:"https://open.spotify.com/artist/6ls5A8Wys9Swixpz4v6kj3"},href:"https://api.spotify.com/v1/artists/6ls5A8Wys9Swixpz4v6kj3",id:"6ls5A8Wys9Swixpz4v6kj3",name:"The Lighthouse And The Whaler",type:"artist",uri:"spotify:artist:6ls5A8Wys9Swixpz4v6kj3"}],album:{name:"Into the Unknown",link:"https://open.spotify.com/album/6NpAhMmG0TkOpapqs6NFnj",image:{url:"https://i.scdn.co/image/ab67616d00001e02e0f91ea6315c150d84e031be"}}},{name:"The Science Love Song",popularity:35,locality:false,link:"https://open.spotify.com/track/0DkvyLEW2VG5e2ruf7kTS9",id:"spotify:track:0DkvyLEW2VG5e2ruf7kTS9",duration:200054,artists:[{external_urls:{spotify:"https://open.spotify.com/artist/7E8gU2qkctwGqHBizPRH9A"},href:"https://api.spotify.com/v1/artists/7E8gU2qkctwGqHBizPRH9A",id:"7E8gU2qkctwGqHBizPRH9A",name:"AsapSCIENCE",type:"artist",uri:"spotify:artist:7E8gU2qkctwGqHBizPRH9A"}],album:{name:"The Science Love Song",link:"https://open.spotify.com/album/13sXGjZuZgXuvjAZfXOXfP",image:{url:"https://i.scdn.co/image/ab67616d00001e02d5e24468d455c3140f0581b2"}}}]; const dev_artists = [{popularity:87,name:"Avicii",link:"https://open.spotify.com/artist/1vCWHaC5f2uS3yhpwWbIA6",image:{url:"https://i.scdn.co/image/9c0d8fa969a9f5db6ff860203d6880a125e501d2"},id:"1vCWHaC5f2uS3yhpwWbIA6",genres:["big room","dance pop","edm","pop"],follower_count:17671212},{popularity:52,name:"'Come From Away' Company",link:"https://open.spotify.com/artist/3yVB4N2PhQy60dAxzCoNdQ",image:undefined,id:"3yVB4N2PhQy60dAxzCoNdQ",genres:["broadway","show tunes"],follower_count:3143}];
|
|
||||||
10
js/prototypes.js
vendored
10
js/prototypes.js
vendored
|
|
@ -1,10 +0,0 @@
|
||||||
String.prototype.toTitleCase = function () {
|
|
||||||
let words = this.split(` `);
|
|
||||||
let new_words = [];
|
|
||||||
for (var word of words) {
|
|
||||||
new_words.push(
|
|
||||||
`${word[0].toUpperCase()}${word.slice(1).toLowerCase()}`
|
|
||||||
);
|
|
||||||
};
|
|
||||||
return new_words.join(` `);
|
|
||||||
}
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
function get_button_text () {
|
|
||||||
if (this.count === ``) {
|
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
else if (this.count === `1`) {
|
|
||||||
this.error.main = ``;
|
|
||||||
return this.type.slice(0,-1);
|
|
||||||
}
|
|
||||||
else if (this.count <= 0) {
|
|
||||||
this.error.main = `Cannot get 0 or fewer ${this.type.toLowerCase()}`;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (this.count > 50) {
|
|
||||||
this.error.main = `Cannot get more than 50 ${this.type.toLowerCase()}`;
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
this.error.main = ``;
|
|
||||||
return this.type;
|
|
||||||
};
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue