Migrate all the things

* Migrates from Mongo to Postgres.
* Migrates from JSPM to Webpack.
* Migrates from React to Vuejs.
* Migrates from Bootstrap to Bulma.

Also:
* Fixes rendering of meta data in the document head tag.
This commit is contained in:
Jonathan Cremin 2016-10-03 13:31:29 +01:00
parent 09706778d9
commit 7bb0497ff4
76 changed files with 6741 additions and 1760 deletions

124
public/src/views/index.vue Normal file
View file

@ -0,0 +1,124 @@
<template>
<div class="home container">
<search></search>
<div class="blurb">
<p>
Match Audio makes sharing from music services better. What happens when you share your favourite song on Spotify with a friend, but they don't use Spotify?
</p>
<p>
We match album and track links from Youtube, Rdio, Spotify, Deezer, Google Music, Xbox Music, Beats Music, and iTunes and give you back one link with matches we find on all of them.
</p>
</div>
<div class="recently-shared">
<h2 class="title is-2">Recently Shared</h2>
<ul class="columns is-multiline">
<li v-for="(item, index) in recents" class="column is-one-third ">
<router-link :to="{ name: 'share', params: { service: item.service, type: item.albumName ? 'track' : 'album', id: item.externalId }}"><div v-bind:style="{ backgroundImage: `url(${item.matches.find(function(el) { return el.service == item.service }).artworkLarge })` }" class="artwork">
</div></router-link>
</li>
</ul>
</div>
<div class="faq">
<h2 class="title is-2">Questions?</h2>
<h3 class="title is-3">Why would I want to use this?</h3>
<p>Sometimes when people want to share music they don't know what service their friends are using. Match Audio let's you take a link from one service and expand it into a link that supports all services.</p>
<h3 class="title is-3">I still don't get it.</h3>
<p>That's not actually a question, but that's ok. Here's an example: I'm listening to a cool new album I found on Google Play Music. So I go to the address bar (the box that sometimes says https://www.google.com in it) and copy the link to share with my friend. But my friend uses Spotify. So first I go to Match Audio and paste the link there, then grab the Match Audio link from the address bar and send them that link instead.</p>
<h3 class="title is-3">Where do I find a link to paste in the box?</h3>
<p>Most music services have a 'share' dialog for albums and tracks in their interface. If you have them open in a web browser instead of an app, you can simply copy and paste the address bar and we'll work out the rest.</p>
<h3 class="title is-3">Can I share playlists?</h3>
<p>Unfortunately not. Playlists would add a huge amount of complexity and would almost certainly cause the site to break the API limits imposed by some of the services we support.</p>
<h3 class="title is-3">Why don't you guys support Bandcamp, Amazon Music, Sony Music Unlimited ?</h3>
<p>Let me stop you there. Match Audio is open source, that means any capable programmer who wants to add other music services can look at our code and submit changes. If you're not a programmer, you can always submit a request and maybe we'll do it for you.</p>
</div>
<div>
<h2 class="title is-2">Tools</h2>
<div class="columns">
<p class="column is-half">
Download the Chrome Extension and get Match Audio links right from your address bar.
</p>
<p class="column is-half">
<a href="https://chrome.google.com/webstore/detail/kjfpkmfgcflggjaldcfnoppjlpnidolk"><img src="/assets/images/chrome-web-store.png" alt="Download the Chrome Extension" /></a>
</p>
</div>
</div>
</div>
</template>
<script>
import { fetchRecents } from '../store/api';
import search from '../components/search.vue';
export default {
name: 'index-view',
components: { search },
created () {
// fetch the data when the view is created and the data is
// already being observed
this.fetch();
},
data() {
return {
recents: {},
};
},
watch: {
'$route': 'fetch',
recents: function () {
if (typeof document !== 'undefined') {
const recents = this.$store.state.recents;
document.title = `Match Audio • Share Music`;
}
},
},
methods: {
fetch () {
if (!this.$store.state.recents) {
fetchRecents().then((res) => {
this.recents = res.body.recents;
});
} else {
this.recents = this.$store.state.recents;
}
},
},
}
</script>
<style>
.blurb {
margin-bottom: 50px;
}
.recently-shared {
margin-bottom: 50px;
}
.faq {
margin-bottom: 50px;
}
.faq p {
margin-bottom: 30px;
}
.home {
width: 600px;
margin-top: 40px;
}
p {
margin-bottom: 10px;
}
.recent .artwork {
margin-bottom: 30px;
}
.artwork {
position: relative;
width: 100%;
height: 0;
padding-bottom: 100%;
background-repeat: no-repeat;
background-size: cover;
border-radius: 5px;
}
</style>

146
public/src/views/share.vue Normal file
View file

@ -0,0 +1,146 @@
<template>
<div class="container" v-if="item.name">
<div class="share-heading">
<h3 class="title is-3">Matched {{ item.albumName ? 'tracks' : 'albums' }} for</h3>
<h2 class="title is-2"><strong>{{ item.name }}</strong> - {{ item.artist.name }}</h2>
</div>
<ul class="columns is-multiline">
<li v-for="match in item.matches" class="column is-2">
<div v-if="match.externalId && match.id != 152">
<a v-bind:href="match.streamUrl"><div v-bind:style="{ backgroundImage: `url(${match.artworkLarge})` }" class="artwork">
</div></a>
<div class='service-link has-text-centered'>
<a v-bind:href="match.streamUrl"><img v-bind:src="`/assets/images/${match.service}.png`" /></a>
</div>
</div>
<div v-if="match.matching || match.id === 152" class="service">
<div v-bind:style="{ backgroundImage: `url(${item.matches[0].artworkLarge})` }" class="artwork">
</div>
<div class='loading-wrap'>
<img src='/assets/images/eq.svg' class='loading' />
</div>
<div class='service-link has-text-centered'>
<img v-bind:src="`/assets/images/${match.service}.png`" />
</div>
</div>
<div class="service" v-if="!match.externalId && !match.matching">
<div v-bind:style="{ backgroundImage: `url(${item.matches[0].artworkLarge})` }" class="artwork not-found">
</div>
<div class='no-match'>
No Match
</div>
<div class='service-link has-text-centered not-found'>
<img v-bind:src="`/assets/images/${match.service}.png`" />
</div>
</div>
</li>
</ul>
</div>
</template>
<script>
import { fetchItem } from '../store/api';
export default {
name: 'share-view',
data() {
return {
item: {},
};
},
created () {
// fetch the data when the view is created and the data is
// already being observed
this.fetch();
this.interval = setInterval(() => {
this.fetch();
}, 1000);
},
watch: {
// call again the method if the route changes
'$route': 'fetch',
},
methods: {
fetch () {
const item = this.$store.state.item;
const id = this.$route.params.id;
if (item && item.externalId === id && (typeof window === 'undefined' || !item.matches.some(match => match.matching))) {
this.item = this.$store.state.item;
} else {
fetchItem(this.$route.params.service, this.$route.params.type, id).then((res) => {
if(!res.body.matches.some(match => match.matching)) {
clearInterval(this.interval);
}
this.item = res.body;
document.title = `Match Audio • ${this.item.artist.name} - ${this.item.name}`;
});
}
}
}
}
</script>
<style>
.share-heading {
margin-bottom: 50px
}
.share-heading .title {
color: #8396b0;
}
.share-heading .title strong {
color: #445470;
font-weight: 700;
}
.artwork {
position: relative;
width: 100%;
height: 0;
padding-bottom: 100%;
background-repeat: no-repeat;
background-size: cover;
background-position-x: center;
border-radius: 5px;
}
.service {
margin-bottom: 10px;
}
.service-link img {
margin-top: 20px;
margin-bottom: 20px;
height: 40px;
}
img {
vertical-align: middle;
}
.not-found {
opacity: 0.2;
}
.match {
position: relative;
}
.no-match {
position: absolute;
top: 10px;
right: 10px;
background: #fff;
color: #FE4365;
padding: 3px 6px;
border-radius: 3px;
opacity: 0.7;
font-weight: bold;
}
.loading-wrap {
position: absolute;
top: 0;left: 0;
background: #fff;
height: 100%;
width: 100%;
opacity: 0.8;
}
.loading {
position: absolute;
top: 35%;
left: 40%;
width: 20%;
}
</style>