Skip to content

Commit

Permalink
Adds styles and store.
Browse files Browse the repository at this point in the history
  • Loading branch information
Cher Stewart committed Feb 13, 2019
1 parent e8511d1 commit 12d451f
Show file tree
Hide file tree
Showing 10 changed files with 538 additions and 1 deletion.
2 changes: 1 addition & 1 deletion api/index.js
Expand Up @@ -137,7 +137,7 @@ app.get('/spotify/now-playing/', async (req, res) => {
setLastPlayed(access_token, response.data)
res.send(response.data)
} catch (err) {
console.log(err)
console.error(err)
res.send({ error: err.message })
}
})
Expand Down
4 changes: 4 additions & 0 deletions assets/spotify.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions assets/twitter.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions components/Footer.vue
@@ -0,0 +1,21 @@
<template>
<footer>
This is a demo built by
<a href="http://cherp.io">Cher Scarlett</a> using Nuxt.js, Redis, and Spotify. Made with 💙.
<a
href="https://github.com/cherscarlett/cherislistening/fork"
>Fork your own 🐙.</a>
</footer>
</template>

<style scoped>
footer {
width: 70%;
min-width: 320px;
max-width: 600px;
margin: 0 auto;
filter: blur(0);
transition: all 600ms ease-out;
padding-bottom: 1em;
}
</style>
55 changes: 55 additions & 0 deletions components/Header.vue
@@ -0,0 +1,55 @@
<template>
<header>
<h1>
{{ $options.title }}
<nuxt-link
to="/auth"
name="auth"
aria-label="Login"
v-bind:aria-current="'/auth' === $nuxt.$route.path ? 'page' : false"
/>
</h1>
</header>
</template>

<script>
export default {
title: 'Cher is Listening'
}
</script>

<style scoped>
h1 {
line-height: 0.65em;
font-family: 'Oswald', monospace;
letter-spacing: 1px;
font-size: 15em;
transform: rotate(-3deg) translateY(-50%);
-webkit-box-reflect: below 0px
linear-gradient(transparent, rgba(255, 255, 255, 0.3));
}
h1 a {
display: inline-block;
height: 20px;
width: 20px;
background-image: url(~assets/spotify.svg);
background-size: cover;
filter: brightness(300%) opacity(30%);
}
h1 a:hover {
filter: brightness(300%) opacity(60%);
}
h1 a:after,
h1 a:before {
content: none;
}
header {
filter: blur(0);
transition: all 600ms ease-out;
}
p {
width: 40%;
margin: auto;
min-width: 320px;
}
</style>
199 changes: 199 additions & 0 deletions components/NowPlaying.vue
@@ -0,0 +1,199 @@
<template>
<transition name="fade">
<section>
<aside>
<img :src="image" alt="Album Artwork">
<Progress :class="isPlaying ? '' : 'is-paused'" :progressPercent="progress" :image="image"/>
</aside>
<div class="metadata">
<h2>{{name}}</h2>
<p>{{artistsList}}</p>
<p :class="isPlaying ? 'is-playing status' : 'status'">
<span>Cher {{ status }}.</span>
<a :href="href">Listen?</a>
</p>
</div>
</section>
</transition>
</template>

<script>
import Progress from './Progress.vue'
export default {
components: { Progress },
props: ['isPlaying', 'nowPlaying'],
data() {
return { staleTimer: '', trackTimer: '' }
},
computed: {
image() {
if (Boolean(this.nowPlaying.album)) {
return this.nowPlaying.album.images[0].url
}
return this.nowPlaying.image
},
progress() {
return this.$store.state.trackProgress
},
artistsList() {
return this.nowPlaying.artists.map(artist => artist.name).join(', ')
},
href() {
return this.nowPlaying.external_urls.spotify
},
name() {
return this.nowPlaying.name
},
status() {
return this.isPlaying
? `is playing this track with ${Math.round(
this.$store.state.trackProgress
)}% complete`
: 'has paused this track'
}
},
created() {
this.getNowPlaying()
this.staleTimer = setInterval(() => {
this.getNowPlaying()
}, 10000)
},
methods: {
updateProgress(progress = 0, duration = 0) {
this.$store.dispatch('updateProgress', { progress, duration })
},
async getNowPlaying() {
const { progress_ms, is_playing, item } = await this.$axios.$get(
`/api/spotify/now-playing/`
)
if (Boolean(item)) {
const progress = progress_ms
const duration = item.duration_ms
this.$store.dispatch('updateStatus', is_playing)
clearInterval(this.trackTimer)
if (is_playing) {
this.timeTrack(Date.now(), duration, progress)
} else {
this.updateProgress(progress, duration)
}
let id = null
if (Boolean(this.nowPlaying)) id = this.nowPlaying.id
if (item && (is_playing && item.id !== id)) {
this.$store.dispatch('updateTrack', item)
}
}
},
timeTrack(now, duration, progress) {
const remainder = duration - progress
const until = now + remainder
this.trackTimer = setInterval(() => {
const newNow = Date.now()
if (newNow < until + 2500) {
const newRemainder = until - newNow
const newProgressMs = duration - newRemainder
this.updateProgress(newProgressMs, duration)
} else {
this.updateProgress(1, 1)
clearInterval(this.trackTimer)
this.getNowPlaying()
}
}, 100)
}
},
beforeDestroy() {
clearInterval(this.staleTimer)
clearInterval(this.trackTimer)
}
}
</script>

<style scoped>
section {
position: relative;
display: grid;
grid-template-columns: 42% 58%;
align-items: center;
justify-content: center;
}
aside {
position: relative;
min-width: 50px;
}
img {
opacity: 0;
position: absolute;
height: 0;
width: 0;
}
section:after,
section:before {
content: '';
display: block;
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
z-index: 0;
}
section:after {
transform: rotate(1deg);
background: rgba(255, 255, 255, 0.1);
}
section:before {
transform: rotate(3deg);
background: rgba(255, 255, 255, 0.03);
}
div.metadata {
padding-left: 1.4em;
position: relative;
z-index: 2;
}
h2 {
font-family: 'Oswald', monospace;
margin: 0;
font-size: 3em;
}
p {
margin: 0;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}
.fade-enter-active {
transition: opacity 600ms ease-out;
}
.fade-leave-active {
opacity: 0;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.status span {
opacity: 0.7;
font-size: 0.8em;
padding: 1em 0;
display: block;
white-space: nowrap;
}
.is-playing span {
opacity: 0;
transition: opacity 600ms ease-out;
}
@media (max-width: 600px) {
section {
grid-template-rows: 42% 58%;
grid-template-columns: 100%;
}
aside {
max-width: 160px;
}
}
</style>
84 changes: 84 additions & 0 deletions components/Progress.vue
@@ -0,0 +1,84 @@
<template>
<div aria-hidden="true">
<svg
class="album"
viewBox="0 0 33.83098862 33.83098862"
xmlns="http://www.w3.org/2000/svg"
role="img"
>
<defs>
<pattern id="image" x="0%" y="0%" height="100%" width="100%" patternUnits="userSpaceOnUse">
<image x="0%" y="0%" width="100%" height="100%" v-bind="{'xlink:href': image }"></image>
</pattern>
</defs>
<circle class="image" cx="16.91549431" cy="17.3" r="15.9"></circle>
</svg>
<svg class="progress" viewBox="0 0 33.83098862 33.83098862" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stop-color="#333642"></stop>
<stop offset="25%" stop-color="rgba(0, 112, 255, 0.8)"></stop>
<stop offset="90%" stop-color="rgba(118, 120, 224, 0.5)"></stop>
<stop offset="100%" stop-color="rgba(255, 255, 255, .5)"></stop>
</linearGradient>
</defs>
<circle
class="bar"
:stroke-dasharray="`${progressPercent} 100`"
fill="none"
cx="16.91549431"
cy="16.91549431"
r="15.91549431"
></circle>
</svg>
</div>
</template>

<script>
export default {
props: ['progressPercent', 'image']
}
</script>

<style scoped>
div {
filter: grayscale(0);
}
.is-paused {
filter: grayscale(80%);
transition: all 600ms ease-out;
}
svg {
height: 100%;
width: 100%;
}
svg.album {
filter: drop-shadow(0 0 10px rgba(0, 0, 0, 0.3));
}
svg.progress {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
filter: drop-shadow(0 0 1px rgba(255, 255, 255, 0.2))
drop-shadow(0 0 2px var(--colorBrightBlue))
drop-shadow(0 0 3px var(--colorBrightBlue))
drop-shadow(0 0 5px var(--colorBrightBlue)) opacity(65%) contrast(150%);
}
.bar {
stroke: url(#gradient);
stroke-width: 0.03em;
transform: rotate(-90deg);
transform-origin: center;
animation: fill 2s reverse;
}
.image {
fill: url(#image);
}
@keyframes fill {
to {
stroke-dasharray: 0 100;
}
}
</style>

0 comments on commit 12d451f

Please sign in to comment.