Skip to content

Instantly share code, notes, and snippets.

@jbgutierrez
Last active December 14, 2015 04:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jbgutierrez/5028740 to your computer and use it in GitHub Desktop.
Save jbgutierrez/5028740 to your computer and use it in GitHub Desktop.
Laboratorio de yeoman y angular (http://yoplay.jbgutierrez.info)
String::pad = (l, s) ->
if (l -= @length) > 0
(s = new Array(Math.ceil(l / s.length) + 1).join(s)).substr(0, s.length) + this + s.substr(0, l - s.length)
else
@
app = angular.module "yoplayApp", ["ngResource"]
app.factory "myHttpInterceptor", ["$q", "$window", "$rootScope", ($q, $window, $rootScope) ->
(promise) ->
$rootScope.loading = true
promise.then ((response) ->
$rootScope.loading = false
response
), (response) ->
$rootScope.loading = false
$q.reject response
]
app.config [ '$httpProvider', ($httpProvider) ->
$httpProvider.responseInterceptors.push "myHttpInterceptor"
]
app.service 'analytics', ->
@trackEvent = (category, action, opt_label) ->
_gaq.push ['_trackEvent', category, action, opt_label.toLowerCase()]
app.factory 'LastFM', ["$resource", ($resource) ->
$resource 'http://ws.audioscrobbler.com/2.0',
{},
get:
method: 'GET'
params:
api_key: '<secret>'
format: 'json'
]
app.factory 'VK', ["$resource", ($resource) ->
$resource 'https://api.vkontakte.ru/method/audio.search',
{},
get:
method: 'JSONP'
params:
offset: 0
count: 50
auto_complete: 1
access_token: '<secret>'
]
app.factory "player", ["$rootScope", "$document", "analytics", "VK", ($rootScope, $document, analytics, VK) ->
player =
audio: null
audio = $document[0].createElement("audio")
$rootScope.canPlayMP3 = angular.isFunction(audio.canPlayType) && audio.canPlayType("audio/mpeg")
if $rootScope.canPlayMP3
audio.addEventListener "ended", (->
$rootScope.$apply ->
player.nextTrack()
), false
player =
audio: audio
loadTrack: (track, album) ->
@album = album if angular.isDefined(album)
@track = track if angular.isDefined(track)
track = @album.tracks[@track]
window.success = (result) =>
tracks = result.response
tracks.shift()
if tracks.length
matches = tracks.filter (t) -> t.duration is +track.duration
source = if matches.length then matches[0] else tracks[0]
audio.src = source.url
audio.play()
analytics.trackEvent 'VK', 'player', 'successed'
else
track.notFound = true
@album.incomplete = true
@nextTrack()
analytics.trackEvent 'VK', 'player', 'failed'
VK.get {q: "#{@album.artist} #{track.name}", callback: 'success'}
nextTrack: ->
@track++
@track = 0 unless @album.tracks.length > @track
@loadTrack()
player
]
app.filter "formatYear", ->
(str = '') ->
tokens = str.match(/\d{4}/)
if tokens then tokens[0] else str
app.filter "humanizeDuration", ->
(number) ->
Math.floor(+number / 60) + ":" + (+number % 60).toFixed().pad(2, "0")
@MainCtrl = ["$scope", "analytics", "LastFM", "player", ($scope, analytics, LastFM, player) ->
$scope.album = null
$scope.player = player
showInfo = (album) ->
LastFM.get {method: 'album.getinfo', mbid: album.mbid}, (result) ->
album = result.album
album.imagePath = album.image[2]['#text']
album.tracks = if angular.isArray(album.tracks.track) then album.tracks.track else [ album.tracks.track ]
$scope.album = album
player.loadTrack 0, album
$scope.search = ->
return unless $scope.criteria
LastFM.get {method: 'artist.gettopalbums', artist: $scope.criteria}, (result) ->
albums = result.topalbums && result.topalbums.album || []
albums = [albums] unless angular.isArray(albums)
albums = albums.filter (a) -> a.mbid
if albums.length
$scope.notFoundCriteria = false
a.imagePath = a.image[1]['#text'] for a in albums
showInfo albums[0]
$scope.albums = albums
analytics.trackEvent 'LastFM', 'search', 'successed'
else
$scope.notFoundCriteria = $scope.criteria
analytics.trackEvent 'LastFM', 'search', 'failed'
analytics.trackEvent 'user', 'search', $scope.criteria
$scope.showInfo = (album) ->
showInfo(album)
analytics.trackEvent 'user', 'showInfo', "#{album.artist} - #{album.name}"
$scope.getColumnStyle = (idx, total) ->
columnIdx = Math.floor(idx * 2 / total)
style = if columnIdx then 'margin-left: 350px' else ''
unless idx == 0
nextColumnIdx = Math.floor((idx - 1) * 2 / total)
style += "; margin-top: -#{Math.ceil(total / 2) * 2}0px" if nextColumnIdx isnt columnIdx
style
]
(function() {
var app;
String.prototype.pad = function(l, s) {
if ((l -= this.length) > 0) {
return (s = new Array(Math.ceil(l / s.length) + 1).join(s)).substr(0, s.length) + this + s.substr(0, l - s.length);
} else {
return this;
}
};
app = angular.module("yoplayApp", ["ngResource"]);
app.factory("myHttpInterceptor", [
"$q", "$window", "$rootScope", function($q, $window, $rootScope) {
return function(promise) {
$rootScope.loading = true;
return promise.then((function(response) {
$rootScope.loading = false;
return response;
}), function(response) {
$rootScope.loading = false;
return $q.reject(response);
});
};
}
]);
app.config([
'$httpProvider', function($httpProvider) {
return $httpProvider.responseInterceptors.push("myHttpInterceptor");
}
]);
app.service('analytics', function() {
return this.trackEvent = function(category, action, opt_label) {
return _gaq.push(['_trackEvent', category, action, opt_label.toLowerCase()]);
};
});
app.factory('LastFM', [
"$resource", function($resource) {
return $resource('http://ws.audioscrobbler.com/2.0', {}, {
get: {
method: 'GET',
params: {
api_key: '<secret>',
format: 'json'
}
}
});
}
]);
app.factory('VK', [
"$resource", function($resource) {
return $resource('https://api.vkontakte.ru/method/audio.search', {}, {
get: {
method: 'JSONP',
params: {
offset: 0,
count: 50,
auto_complete: 1,
access_token: '<secret>'
}
}
});
}
]);
app.factory("player", [
"$rootScope", "$document", "analytics", "VK", function($rootScope, $document, analytics, VK) {
var audio, player;
player = {
audio: null
};
audio = $document[0].createElement("audio");
$rootScope.canPlayMP3 = angular.isFunction(audio.canPlayType) && audio.canPlayType("audio/mpeg");
if ($rootScope.canPlayMP3) {
audio.addEventListener("ended", (function() {
return $rootScope.$apply(function() {
return player.nextTrack();
});
}), false);
player = {
audio: audio,
loadTrack: function(track, album) {
var _this = this;
if (angular.isDefined(album)) {
this.album = album;
}
if (angular.isDefined(track)) {
this.track = track;
}
track = this.album.tracks[this.track];
window.success = function(result) {
var matches, source, tracks;
tracks = result.response;
tracks.shift();
if (tracks.length) {
matches = tracks.filter(function(t) {
return t.duration === +track.duration;
});
source = matches.length ? matches[0] : tracks[0];
audio.src = source.url;
audio.play();
return analytics.trackEvent('VK', 'player', 'successed');
} else {
track.notFound = true;
_this.album.incomplete = true;
_this.nextTrack();
return analytics.trackEvent('VK', 'player', 'failed');
}
};
return VK.get({
q: "" + this.album.artist + " " + track.name,
callback: 'success'
});
},
nextTrack: function() {
this.track++;
if (!(this.album.tracks.length > this.track)) {
this.track = 0;
}
return this.loadTrack();
}
};
}
return player;
}
]);
app.filter("formatYear", function() {
return function(str) {
var tokens;
if (str == null) {
str = '';
}
tokens = str.match(/\d{4}/);
if (tokens) {
return tokens[0];
} else {
return str;
}
};
});
app.filter("humanizeDuration", function() {
return function(number) {
return Math.floor(+number / 60) + ":" + (+number % 60).toFixed().pad(2, "0");
};
});
this.MainCtrl = [
"$scope", "analytics", "LastFM", "player", function($scope, analytics, LastFM, player) {
var showInfo;
$scope.album = null;
$scope.player = player;
showInfo = function(album) {
return LastFM.get({
method: 'album.getinfo',
mbid: album.mbid
}, function(result) {
album = result.album;
album.imagePath = album.image[2]['#text'];
album.tracks = angular.isArray(album.tracks.track) ? album.tracks.track : [album.tracks.track];
$scope.album = album;
return player.loadTrack(0, album);
});
};
$scope.search = function() {
if (!$scope.criteria) {
return;
}
LastFM.get({
method: 'artist.gettopalbums',
artist: $scope.criteria
}, function(result) {
var a, albums, _i, _len;
albums = result.topalbums && result.topalbums.album || [];
if (!angular.isArray(albums)) {
albums = [albums];
}
albums = albums.filter(function(a) {
return a.mbid;
});
if (albums.length) {
$scope.notFoundCriteria = false;
for (_i = 0, _len = albums.length; _i < _len; _i++) {
a = albums[_i];
a.imagePath = a.image[1]['#text'];
}
showInfo(albums[0]);
$scope.albums = albums;
return analytics.trackEvent('LastFM', 'search', 'successed');
} else {
$scope.notFoundCriteria = $scope.criteria;
return analytics.trackEvent('LastFM', 'search', 'failed');
}
});
return analytics.trackEvent('user', 'search', $scope.criteria);
};
$scope.showInfo = function(album) {
showInfo(album);
return analytics.trackEvent('user', 'showInfo', "" + album.artist + " - " + album.name);
};
return $scope.getColumnStyle = function(idx, total) {
var columnIdx, nextColumnIdx, style;
columnIdx = Math.floor(idx * 2 / total);
style = columnIdx ? 'margin-left: 350px' : '';
if (idx !== 0) {
nextColumnIdx = Math.floor((idx - 1) * 2 / total);
if (nextColumnIdx !== columnIdx) {
style += "; margin-top: -" + (Math.ceil(total / 2) * 2) + "0px";
}
}
return style;
};
}
];
}).call(this);
<!doctype html>
<!--[if lt IE 7]> <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]> <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]> <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"/>
<title>YoPlay - Música online</title>
<meta name="description" content="Catálogo musical proporcionado por LastFM y VK">
<meta name="viewport" content="width=960">
<!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
<link rel="stylesheet" href="styles/styles.css">
</head>
<body ng-app="yoplayApp" ng-controller="MainCtrl">
<!--[if lt IE 7]>
<p class="chromeframe">You are using an outdated browser. <a href="http://browsehappy.com/">Upgrade your browser today</a> or <a href="http://www.google.com/chromeframe/?redirect=true">install Google Chrome Frame</a> to better experience this site.</p>
<![endif]-->
<!--[if lt IE 9]>
<script src="components/es5-shim/es5-shim.js"></script>
<script src="components/json3/lib/json3.min.js"></script>
<![endif]-->
<p class="browser-not-supported" style='display:none' ng-hide='canPlayMP3'>Tu navegador no soporta la reproducción de ficheros mp3. Usa Chrome, IE &gt; 8 o Safari.</p>
<div class="loading" style='display:none' ng-show='loading'>
<p>cargando <img src='/images/ajax-loader.gif' /> </p>
</div>
<div class='container'>
<header class="header">
<h1 id='logo'>
<span>YoPlay</span>
<img src='/images/logo.jpg' alt='YoPlay logo'/>
</h1>
<p class='lead'>Música por cortesía de <a href='http://yeoman.io' target='_blank'>Yeoman</a>, <a href='http://www.lastfm.es' target='_blank'>LastFM</a> y <a href='http://vk.com/' target='_blank'>VK</a>.</p>
<form ng-submit="search()">
<input type="text" class='input-xlarge' ng-model="criteria" placeholder='¿Qué artista te apetece escuchar?' />
<button class="btn btn-large btn-success">Reproducir</button>
<p class='error' style='display:none' ng-show="notFoundCriteria">* Lo siento pero no encuentro discos de "{{notFoundCriteria}}" en LastFM</p>
</form>
</header>
<section class="library" style='display:none' ng-show="albums.length">
<img class='image' ng-src="{{album.imagePath}}" title='{{album.name}}' ng-repeat="album in albums" ng-click="showInfo(album)"/>
</section>
<section class="album-info" style='display:none' ng-show="album">
<img class='image' ng-src="{{album.imagePath}}" />
<div class="details">
<div class="page-header">
<h2>
<span>
<i class="control icon-play" title='Reproducir' ng-click="player.audio.play()" ng-show="canPlayMP3 && player.audio.paused"></i>
<i class="control icon-pause" title='Pausar' ng-click="player.audio.pause()" ng-hide="!canPlayMP3 || player.audio.paused"></i>
{{ album.name }}
</span>
<small>{{ album.releasedate | formatYear }}</small>
</h2>
</div>
<hr>
<ol class='tracks'
><li class='track' style='{{ getColumnStyle($index, album.tracks.length) }}' ng-repeat="track in album.tracks" ng-class="{current: canPlayMP3 && $index==player.track, 'not-found': track.notFound}" ng-click="player.loadTrack($index)">
<span class='number muted'>{{ $index + 1 }}</span>
<span class='name'>{{ track.name }}</span>
<span class='duration muted'>{{ track.duration | humanizeDuration }}</span>
</li
></ol>
<p class='error' style='display:none' ng-show="album.incomplete">* Parece que VK no están todas las canciones del disco</p>
</div>
</section>
</div>
<!-- build:js scripts/scripts.js -->
<script src="components/angular/angular.js"></script>
<script src="components/angular-resource/angular-resource.js"></script>
<script src="scripts/app.js"></script>
<!-- endbuild -->
<script>
var _gaq=[['_setAccount','UA-XXXXXXXX-1'],['_trackPageview']];
(function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];
g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
s.parentNode.insertBefore(g,s)}(document,'script'));
</script>
</body>
</html>
@import "compass";
/* Estilos procedentes de twitter bootstrap */
@import "compass_twitter_bootstrap/mixins";
@import "compass_twitter_bootstrap/reset";
@import "compass_twitter_bootstrap/variables";
.btn {
display: inline-block;
@include ie7-inline-block();
padding: 4px 14px;
margin-bottom: 0; // For input.btn
font-size: $baseFontSize;
line-height: $baseLineHeight;
*line-height: $baseLineHeight;
text-align: center;
vertical-align: middle;
cursor: pointer;
@include buttonBackground($btnBackground, $btnBackgroundHighlight, $grayDark, 0 1px 1px rgba(255,255,255,.75));
border: 1px solid $btnBorder;
*border: 0; // Remove the border to prevent IE7's black border on input:focus
border-bottom-color: darken($btnBorder, 10%);
@include border-radius(4px);
@include ie7-restore-left-whitespace(); // Give IE7 some love
@include box-shadow(#{inset 0 1px 0 rgba(255,255,255,.2), 0 1px 2px rgba(0,0,0,.05)});
// Hover state
&:hover {
color: $grayDark;
text-decoration: none;
background-color: darken($white, 10%);
*background-color: darken($white, 15%); /* Buttons in IE7 don't get borders, so darken on hover */
background-position: 0 -15px;
// transition is only when going to hover, otherwise the background
// behind the gradient (there for IE<=9 fallback) gets mismatched
@include transition(background-position .1s linear);
}
// Focus state for keyboard and accessibility
&:focus {
@include tab-focus();
}
// Active state
&.active,
&:active {
background-color: darken($white, 10%);
background-color: darken($white, 15%) \9;
background-image: none;
outline: 0;
@include box-shadow(#{inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)});
}
// Disabled state
&.disabled,
&[disabled] {
cursor: default;
background-color: darken($white, 10%);
background-image: none;
@include opacity(65);
@include box-shadow(none);
}
}
// Button Sizes
// --------------------------------------------------
// Large
.btn-large {
padding: 9px 14px;
font-size: $baseFontSize + 2px;
line-height: normal;
@include border-radius(5px);
}
// Success appears as green
.btn-success {
@include buttonBackground($btnSuccessBackground, $btnSuccessBackgroundHighlight);
}
body {
margin: 0;
font-family: $baseFontFamily;
font-size: $baseFontSize;
line-height: $baseLineHeight;
color: $textColor;
background-color: $bodyBackground;
}
h1, h2 {
margin: ($baseLineHeight / 2) 0;
font-family: $headingsFontFamily;
font-weight: $headingsFontWeight;
line-height: 1;
color: $headingsColor;
text-rendering: optimizelegibility; // Fix the character spacing for headings
small {
font-weight: normal;
line-height: 1;
color: $grayLight;
}
}
h1 {
xdisplay: none;
}
ul, ol {
padding: 0;
margin: 0 0 $baseLineHeight / 2 25px;
}
li {
line-height: $baseLineHeight;
}
hr {
margin: $baseLineHeight 0;
border: 0;
border-top: 1px solid $hrBorder;
border-bottom: 1px solid $white;
}
form {
margin: 0 0 $baseLineHeight;
}
input[type="text"] {
display: inline-block;
height: $baseLineHeight;
padding: 4px 6px;
margin-bottom: 9px;
font-size: $baseFontSize;
line-height: $baseLineHeight;
color: $gray;
@include border-radius($inputBorderRadius);
background-color: $inputBackground;
border: 1px solid $inputBorder;
@include box-shadow(inset 0 1px 1px rgba(0,0,0,.075));
@include transition(#{border linear .2s, box-shadow linear .2s});
// Focus state
&:focus {
border-color: rgba(82,168,236,.8);
outline: 0;
outline: thin dotted \9; /* IE6-9 */
@include box-shadow(#{inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(82,168,236,.6)});
}
@include placeholder();
width: 270px;
}
.muted {
color: #999999;
}
/* Estilos específicos de la aplicación */
@import "sprites";
.lead {
margin-bottom: $baseLineHeight;
font-size: 20px;
font-weight: 200;
line-height: $baseLineHeight * 1.5;
}
a {
@include unstyled-link;
}
.error {
color: $errorText;
font-size: 14px;
font-style: italic;
padding: 10px;
text-align: center;
}
.browser-not-supported {
@extend .error;
background-color: $errorBackground;
border: 1px solid $errorBorder;
}
.loading {
position: fixed;
top: 0;
width: 100%;
p {
background-color: $warningBackground;
color: $warningText;
border: 1px solid $warningBorder;
width: 80px;
padding: 2px 10px;
margin: 0 auto;
font-size: 12px;
text-align: center;
}
}
#logo {
@include hide-text();
img {
height: 124px;
width: 350px;
margin-left: -35px;
}
}
.container {
width: 940px;
margin: 0 auto;
}
.header {
margin: 60px 0 30px;
text-align: center;
form input {
margin-top: 8px;
height: 28px;
font-size: 16px;
}
}
.library {
margin: 30px 128px;
height: 128px;
overflow-y: scroll;
text-align: center;
img {
width: 64px;
height: 64px;
cursor: pointer;
}
}
.album-info {
.image {
width: 220px;
float: left;
margin-left: 20px;
}
.details {
margin-left: 260px;
}
hr {
margin: 10px 0px;
}
h2 {
font-size: 16px;
margin: 0;
}
.control {
cursor: pointer;
vertical-align: top;
margin-top: 3px;
}
small {
float: right;
font-size: 14px;
}
.tracks {
margin: 0 10px;
li {
padding: 0 14px;
}
}
img {
margin-top: 10px;
@include single-box-shadow(rgba(0, 0, 0, 0.5), 1px, 1px, 14px);
}
li {
width: 300px;
font-size: 12px;
line-height: 20px;
height: 20px;
cursor: pointer;
overflow: hidden;
display: block;
list-style: none;
&.current {
background-color: #f5f5f5;
}
.number {
text-align: right;
width: 30px;
margin-right: 10px;
}
.duration {
float: right;
}
.name {
width: 230px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
word-wrap: normal;
display: inline-block;
}
&.not-found {
.name {
text-decoration: line-through;
}
}
}
.error {
font-size: 12px;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment