1
0
Fork 0

Player library, unified renderer for artists and playlists

This commit is contained in:
magnolia-fan 2011-11-27 16:37:51 +04:00
parent 453e809646
commit 9ec28708c5
23 changed files with 174 additions and 122 deletions

View File

@ -11,26 +11,6 @@ class window.Ajax
$('#artist-load-spinner').hide() $('#artist-load-spinner').hide()
false false
load404Page: ->
$.get '/404.html', (data) ->
$('.data-container .inner').html data
false
loadIndexPage: ->
$('#content').load '/greetings/'
_ajax.setTitle ''
false
loadAboutPage: ->
$('#content').load '/about/'
_ajax.setTitle 'About'
false
loadStatPage: ->
$('#content').load '/stat/'
_ajax.setTitle 'Statistics'
false
setArchor: (anchor) -> setArchor: (anchor) ->
@referer = this.getAnchor() @referer = this.getAnchor()
window.location.hash = '#' +anchor window.location.hash = '#' +anchor
@ -66,6 +46,10 @@ class window.Ajax
_session.query path, {}, (response) -> _session.query path, {}, (response) ->
_page.render response _page.render response
_ajax.hideSpinner() _ajax.hideSpinner()
if _session.getUser().id?
$('.button-container').show()
else
$('.button-container').hide()
false false
false false

View File

@ -34,7 +34,7 @@ class window.BeatHaven
logs: [] logs: []
init: -> init: ->
log "Initiating BeatHaven ..." log "Initializing BeatHaven ..."
window._vkontakte = new Vkontakte(2335068) window._vkontakte = new Vkontakte(2335068)
window._vkontakte.init() window._vkontakte.init()

View File

@ -1,8 +1,8 @@
class window.Page class window.Page
@data: {} data: {}
@html: "" html: ""
@status: "" status: ""
render: (params) -> render: (params) ->
@data = params.data @data = params.data
@ -10,9 +10,13 @@ class window.Page
@title = params.title @title = params.title
@status = params.status @status = params.status
log "Rendering page titled \"#{@title}\" of type \"#{params.renderer}\" ..." log "Rendering page titled \"#{@title}\" with #{params.renderer} renderer ..."
$('#content').html(@html) $('#content').html(@html)
if params.callback?
log "Executing page callback action #{params.callback.object}##{params.callback.action} ..."
window["_"+params.callback.object][params.callback.action](@data)
# yaCounter7596904.hit _ajax.getAnchor(), @title, _ajax.referer # yaCounter7596904.hit _ajax.getAnchor(), @title, _ajax.referer
_ajax.setTitle @title _ajax.setTitle @title
false false
@ -46,15 +50,6 @@ class window.Page
false false
$ -> $ ->
$('#logo').live 'click', ->
_ajax.go '/'
false
$('.about').live 'click', ->
_ajax.go '/about/'
false
$('.stat').live 'click', ->
_ajax.go '/stat/'
false
$('body').live 'click', -> $('body').live 'click', ->
$('.dropdown-toggle, .menu').parent('li').removeClass('open') $('.dropdown-toggle, .menu').parent('li').removeClass('open')
false false
@ -64,6 +59,9 @@ $ ->
$('.dropdown-menu li a').live 'click', -> $('.dropdown-menu li a').live 'click', ->
$(this).parent().parent().parent().toggleClass('open') $(this).parent().parent().parent().toggleClass('open')
false false
$('.page-link').live 'click', (e) ->
_ajax.go $(this).attr("href")
false
$('.foreign-link').live 'click', (e) -> $('.foreign-link').live 'click', (e) ->
# window.open($(this).attr('href')) # window.open($(this).attr('href'))
e.preventDefault(); e.preventDefault();

View File

@ -5,6 +5,7 @@ class window.Player
scrobbled: false scrobbled: false
albums: [] albums: []
playlist: [] playlist: []
library: {}
initJplayer: -> initJplayer: ->
self = this self = this
@ -48,24 +49,25 @@ class window.Player
self.setTrack next self.setTrack next
false false
addTracks: (tracks, autoplay) -> addTrack: (item, autoplay) ->
if typeof item in ["number", "string"]
item = @library[item]
if not autoplay? if not autoplay?
autoplay = false autoplay = false
initial_count = $('.playlist li').length initial_count = $('.playlist li').length
for item in tracks len = parseInt(item.length, 10)
len = parseInt(item.length, 10) m = Math.floor(len / 60)
m = Math.floor(len / 60) s = len - Math.floor(len / 60) * 60
s = len - Math.floor(len / 60) * 60 duration = m + ':' + (if s < 10 then '0' else '') + s
duration = m + ':' + (if s < 10 then '0' else '') + s item_class = (if item.available == false then 'unavailable' else '')
item_class = (if item.available == false then 'unavailable' else '') $('.playlist').append "
$('.playlist').append " <li id='i#{Math.round(Math.random() * 999999)}' data-id='#{item.id}' class='#{item_class}'>
<li id='i#{Math.round(Math.random() * 999999)}' data-id='#{item.id}' class='#{item_class}'> <div class='song-duration'>#{duration}</div>
<div class='song-duration'>#{duration}</div> <div class='label important remove'>remove</div>
<div class='label important remove'>remove</div> <div class='artist-name'><a class='data artist'>#{item.artist}</a></div>
<div class='artist-name'><a class='data artist'>#{item.artist}</a></div> <div class='song-title'>#{item.name}</div>
<div class='song-title'>#{item.name}</div> </li>"
</li>" _player.playlist.push item
_player.playlist.push item
$('.playlist').sortable axis: 'y', cursor: 'move' $('.playlist').sortable axis: 'y', cursor: 'move'
if initial_count == 0 and not _player.hasTrack() if initial_count == 0 and not _player.hasTrack()
_player.setTrack($('.playlist li').first().attr('id').split('i')[1]) _player.setTrack($('.playlist li').first().attr('id').split('i')[1])
@ -197,14 +199,18 @@ class window.Player
setPlaylist: (data) -> setPlaylist: (data) ->
this.emptyPlaylist() this.emptyPlaylist()
tracks = [] for track in data.tracks
for item in data.playlist_items this.addTrack track
tracks.push false
id: item.track.id
name: item.track.name updateLibrary: (data) ->
artist: item.track.artists[0].name if data.albums?
length: item.track.length for album in data.albums
this.addTracks tracks for track in album.tracks
@library[track.id] = track
if data.tracks?
for track in data.tracks
@library[track.id] = track
false false
@ -257,36 +263,18 @@ $('.playlist li').live 'click', ->
# Adding To Playlist actions # Adding To Playlist actions
$('.add-album').live 'click', -> $('.add-album').live 'click', ->
album = _player.getAlbumInfo($(this).attr('data-album-id')) for id in $(this).data('tracks').split(",")
for item in album.tracks _player.addTrack id
item['artist'] = album.artist
item['album'] = album.name
_player.addTracks album.tracks
false false
$('.s-add').live 'click', -> $('.s-add').live 'click', ->
album = _player.getAlbumInfo($(this).attr('data-album-id')) _player.addTrack $(this).data('id')
item = album.tracks[$(this).attr('data-id')]
for item in album.tracks
if item.id == parseInt($(this).attr('data-id'), 10)
item['artist'] = album.artist
item['album'] = album.name
_player.addTracks [item]
return false
false false
$('.set-playlist').live 'click', -> $('.set-playlist').live 'click', ->
_search.showSpinner() _search.showSpinner()
$.get "/playlist/#{$(this).data('playlist-id')}", (playlist) -> $.get "/playlist/#{$(this).data('playlist')}", (response) ->
_player.setPlaylist playlist _player.setPlaylist response.data
_search.hideSpinner()
false
false
$('.dynamic-playlist') .live 'click', ->
_search.showSpinner()
$.get "/playlist/#{$(this).data('playlist')}", (playlist) ->
_player.setPlaylist playlist
_search.hideSpinner() _search.hideSpinner()
false false
false false

View File

@ -12,35 +12,29 @@ class window.Search
false false
loadArtistData: (name) -> loadArtistData: (name) ->
log "Loading artist page ..."
_search.showSpinner() _search.showSpinner()
name = name.split(' ').join('+') name = name.split(' ').join('+')
$.get '/artist/' +name+ '/', (data) -> $.get '/artist/' +name+ '/', (data) ->
if data.status in ['ok', 'loading'] if data.status in ['ok', 'loading']
_ajax.setArchor '/artist/' +name+ '/' _page.render data
_page.print data.html
if _session.getUser().id
if data.artist.albums?
for album in data.artist.albums
album['artist'] = data.artist.name
_player.albums.push(album)
$('.button-container').show()
_search.hideSpinner() _search.hideSpinner()
if _session.getUser().id?
$('.button-container').show()
else
$('.button-container').hide()
if data.status is 'loading' if data.status is 'loading'
setTimeout () -> setTimeout () ->
_search.loadArtistData name _search.loadArtistData name
, 10000 , 10000
_ajax.setTitle $('#main h2').text()
else if data.status is 'corrected' else if data.status is 'corrected'
_search.loadArtistData data.correct_name _search.loadArtistData data.correct_name
else if data.status is 'suggestions'
_search.hideSpinner()
_page.print data.html
else if data.status == 'fail'
_search.hideSpinner()
_page.print data.html
else if data.status == 'not_found' else if data.status == 'not_found'
_search.hideSpinner() _search.hideSpinner()
alert "Not found" alert "Not found"
else
_search.hideSpinner()
_page.print data.html
false false
$('#search-form').live 'submit', -> $('#search-form').live 'submit', ->
@ -48,5 +42,5 @@ $('#search-form').live 'submit', ->
_search.loadArtistData $('#search').val() _search.loadArtistData $('#search').val()
false false
$('.data.artist').live 'click', -> $('.data.artist').live 'click', ->
_search.loadArtistData $(this).html() _ajax.go "/artist/"+$(this).html().split(' ').join('+')
false false

View File

@ -6,7 +6,6 @@ class window.Settings
false false
_session.secureLoad '/settings/', (data) -> _session.secureLoad '/settings/', (data) ->
false false
#yaCounter7596904.hit _ajax.getAnchor(), 'Settings', _ajax.referer
_ajax.setTitle 'Settings' _ajax.setTitle 'Settings'
false false

View File

@ -8,7 +8,7 @@ class window.Vkontakte
@api_id @api_id
init: -> init: ->
log "Initiating Vkontakte API ..." log "Initializing Vkontakte API ..."
window.vkAsyncInit = -> window.vkAsyncInit = ->
VK.init apiId: _vkontakte.getApiId() VK.init apiId: _vkontakte.getApiId()
_vkontakte.auth() _vkontakte.auth()

View File

@ -31,6 +31,7 @@
margin-bottom: 20px; margin-bottom: 20px;
.art { .art {
img { img {
width: 100%;
max-width: 220px; max-width: 220px;
} }
.button-container { .button-container {
@ -46,6 +47,15 @@
tr.unavailable > td { tr.unavailable > td {
background-color: #FFF0F0; background-color: #FFF0F0;
} }
td.album-pic {
padding: 0;
line-height: 0;
height: 40px;
width: 40px;
img {
height: 100%;
}
}
td.song-title { td.song-title {
padding: 12px 10px 10px 9px; padding: 12px 10px 10px 9px;
} }

View File

@ -16,7 +16,8 @@ class ApplicationController < ActionController::Base
data: @data, data: @data,
html: render_compact_partial(params[:partial]), html: render_compact_partial(params[:partial]),
title: params[:title], title: params[:title],
status: (params[:status] unless params[:status].nil?) status: (params[:status] unless params[:status].nil?),
callback: (params[:callback] unless params[:callback].nil?)
}, include: (params[:include] unless params[:include].nil?) }, include: (params[:include] unless params[:include].nil?)
end end
@ -24,7 +25,7 @@ protected
def authorize def authorize
unless Vkontakte.check(params) unless Vkontakte.check(params)
render :json => { :status => 'login failed' } render json: { status: 'login failed' }, status: 403
end end
end end

View File

@ -32,11 +32,14 @@ class ArtistController < ApplicationController
return render json: { status: 'fail', html: render_compact_partial(:fail) } return render json: { status: 'fail', html: render_compact_partial(:fail) }
end end
render json: { cache_for 1.week
compile_page(
data: @artist.serialize,
partial: "artist/page",
title: @artist.name,
status: @artist.status_str, status: @artist.status_str,
artist: @artist, callback: {object: :player, action: :updateLibrary}
html: render_compact_partial(:page) )
}, include: { albums: { include: { tracks: {}}}}
end end
private private

View File

@ -14,10 +14,10 @@ class LastFmController < ApplicationController
session.user.save session.user.save
render :text => '<script>window.close();</script>' render :text => '<script>window.close();</script>'
else else
return render :text => 'You Don\'t Fool Me' return render :text => 'You Don\'t Fool Me', status: 403
end end
else else
return render :text => 'So Much Trouble In The World' return render :text => 'So Much Trouble In The World', status: 403
end end
end end
@ -41,7 +41,7 @@ class LastFmController < ApplicationController
def listening def listening
@res = {} @res = {}
if params[:artist].nil? or params[:album].nil? or params[:name].nil? if params[:artist].nil? or params[:album].nil? or params[:name].nil?
return render :json => { :status => 'bad params' } return render :json => { :status => 'bad params' }, status: 403
end end
user = User.find_by_vkid(params[:mid]) user = User.find_by_vkid(params[:mid])
if user.lastfm_key.nil? if user.lastfm_key.nil?
@ -51,17 +51,17 @@ class LastFmController < ApplicationController
:track => params[:name], :track => params[:name],
:artist => params[:artist], :artist => params[:artist],
:album => params[:album], :album => params[:album],
:trackNumber => params[:position].to_i, :trackNumber => params[:position].gsub(/[a-z]/, "").to_i,
:duration => params[:length].to_i, :duration => params[:length].to_i,
:sk => user.lastfm_key # Auth session key :sk => user.lastfm_key # Auth session key
) )
render :json => { :status => r['error'].nil? ? 'success' : r } render :json => { :status => r['error'].nil? ? 'success' : r }, :status => r['error'].nil? ? 200 : 403
end end
def scrobble def scrobble
@res = {} @res = {}
if params[:artist].nil? or params[:album].nil? or params[:name].nil? if params[:artist].nil? or params[:album].nil? or params[:name].nil?
return render :json => { :status => 'bad params' } return render :json => { :status => 'bad params' }, status: 403
end end
user = User.find_by_vkid(params[:mid]) user = User.find_by_vkid(params[:mid])
if user.lastfm_key.nil? if user.lastfm_key.nil?
@ -72,11 +72,11 @@ class LastFmController < ApplicationController
:timestamp => Time.now.utc.to_i, :timestamp => Time.now.utc.to_i,
:artist => params[:artist], :artist => params[:artist],
:album => params[:album], :album => params[:album],
:trackNumber => params[:position].to_i, :trackNumber => params[:position].gsub(/[a-z]/, "").to_i,
:duration => params[:length].to_i, :duration => params[:length].to_i,
:sk => user.lastfm_key # Auth session key :sk => user.lastfm_key # Auth session key
) )
render :json => { :status => r['error'].nil? ? 'success' : r } render :json => { :status => r['error'].nil? ? 'success' : r }, :status => r['error'].nil? ? 200 : 403
end end
def self.top_playlist artist def self.top_playlist artist

View File

@ -8,13 +8,18 @@ class PlaylistController < ApplicationController
artist = Artist.find_by_id(params[:id]) artist = Artist.find_by_id(params[:id])
return if artist.nil? return if artist.nil?
playlist = Playlist.new(name: "#{artist.name}: Last.fm TOP 50", artist: artist) playlist = Playlist.new(name: "#{artist.name}: Last.fm TOP 50", artist: artist, pic_url: artist.pic_url)
LastFM::Artist.get_top_tracks(artist: artist.name)["toptracks"]["track"].each do |track| LastFM::Artist.get_top_tracks(artist: artist.name)["toptracks"]["track"].each do |track|
tracks = Track.joins(:album, :artists).where(name: track["name"], "track_artists.artist_id" => artist.id) tracks = Track.joins(:album, :artists).where(name: track["name"], "track_artists.artist_id" => artist.id)
playlist.playlist_items << PlaylistItem.new(track_id: tracks.first.id) unless tracks.empty? playlist.playlist_items << PlaylistItem.new(track_id: tracks.first.id) unless tracks.empty?
end end
cache_for 1.week cache_for 1.week
render json: playlist, include: { playlist_items: { include: { track: { include: { artists: {} }}}}} compile_page(
data: playlist.serialize,
partial: "playlist/tracks",
title: playlist.name,
callback: {object: :player, action: :updateLibrary}
)
end end
end end

View File

@ -2,4 +2,12 @@ class Playlist < ActiveRecord::Base
belongs_to :user belongs_to :user
belongs_to :artist belongs_to :artist
has_many :playlist_items, dependent: :destroy has_many :playlist_items, dependent: :destroy
def serialize
data = {name: name, pic_url: pic_url, tracks: []}
playlist_items.each do |item|
data[:tracks] << item.track.serialize
end
data
end
end end

View File

@ -9,4 +9,13 @@ class Album < ActiveRecord::Base
def self.with_format f def self.with_format f
joins(:release_formats).where(:release_formats => { :hash => f.to_s }) joins(:release_formats).where(:release_formats => { :hash => f.to_s })
end end
def serialize
{
name: name,
pic_url: pic_url,
year: year,
tracks: tracks.map(&:serialize)
}
end
end end

View File

@ -20,6 +20,15 @@ class Artist < ActiveRecord::Base
Delayed::Job.enqueue(LoadArtistJob.new(name)) Delayed::Job.enqueue(LoadArtistJob.new(name))
end end
def serialize
{
name: name,
pic_url: pic_url,
desc: desc,
albums: albums.map(&:serialize)
}
end
private private
def prepare_description def prepare_description

View File

@ -14,4 +14,18 @@ class Track < ActiveRecord::Base
'0:00' '0:00'
end end
end end
def serialize
{
id: id,
artist: artists.first.name,
album: album.name,
position: position.to_s(36),
name: name,
length: length,
duration: duration,
available: available,
album_pic: album.pic_url
}
end
end end

View File

@ -24,13 +24,13 @@
%table.zebra-striped %table.zebra-striped
%tr %tr
%td %td
%span.label.success.dynamic-playlist{ href: "", 'data-playlist' => "lastfm-top50/#{@artist.id}" }= t('player.set_playlist') %span.label.success.set-playlist{ href: "", 'data-playlist' => "lastfm-top50/#{@artist.id}" }= t('player.set_playlist')
%a.playlist-name{ href: "", 'data-playlist' => "lastfm-top50/#{@artist.id}" }= "#{@artist.name}: Last.fm TOP" %a.page-link.playlist-name{ href: "/playlist/lastfm-top50/#{@artist.id}" }= "#{@artist.name}: Last.fm TOP"
- @artist.playlists.each do |playlist| - @artist.playlists.each do |playlist|
%tr %tr
- @artist.playlists.each do |playlist| - @artist.playlists.each do |playlist|
%td %td
%span.label.success.set-playlist{ href: "", 'data-playlist-id' => playlist.id }= t('player.set_playlist') %span.label.success.set-playlist{ href: "", 'data-playlist' => playlist.id }= t('player.set_playlist')
%a.playlist-name{ href: "", 'data-playlist-id' => playlist.id }= playlist.name %a.playlist-name{ href: "", 'data-playlist-id' => playlist.id }= playlist.name
- @artist.albums.each do |album| - @artist.albums.each do |album|
@ -38,7 +38,7 @@
.span4.columns.art .span4.columns.art
%img{ src: album.pic_url } %img{ src: album.pic_url }
.button-container .button-container
%a.btn.add-album{ 'data-album-id' => album.id }= t('player.add') %a.btn.add-album{ 'data-tracks' => album.tracks.map(&:id).join(",") }= t('player.add')
.span7.columns.tracks .span7.columns.tracks
%h3{ 'data-album-id' => album.id } %h3{ 'data-album-id' => album.id }
= album.name = album.name

View File

@ -17,14 +17,14 @@
.topbar-inner .topbar-inner
.container .container
%h3 %h3
%a#logo{ :href => "#/" } BeatHaven %a#logo.page-link{ :href => "/" } BeatHaven
%ul.nav %ul.nav
%li %li
%a{ :href => "http://blog.beathaven.org/", :target => "_blank" }= t('global.news') %a{ :href => "http://blog.beathaven.org/", :target => "_blank" }= t('global.news')
%li %li
%a.about{ :href => "#/about/" }= t('global.about') %a.page-link.about{ :href => "/about/" }= t('global.about')
%li %li
%a.stat{ :href => "#/stat/" }= t('global.stat') %a.page-link.stat{ :href => "/stat/" }= t('global.stat')
%form#search-form{ :action => "" } %form#search-form{ :action => "" }
%input#search{ :type => "text", :placeholder => t('global.search') } %input#search{ :type => "text", :placeholder => t('global.search') }
#artist-load-spinner #artist-load-spinner

View File

@ -0,0 +1,20 @@
.row.album
.span3.columns.art
- unless @data[:pic_url].nil?
%img{ src: @data[:pic_url] }
.button-container
%a.btn.add-album{ 'data-tracks' => @data[:tracks].map{|_|_[:id]}.join(",") }= t('player.play_all')
.span8.columns.tracks
%h3= @data[:name]
%table.zebra-striped.tracklist
- @data[:tracks].each do |track|
%tr{ class: (track[:available] == false ? "unavailable" : nil) }
%td.album-pic
%img{ src: track[:album_pic]}
%td.song-title
%a.data.artist= track[:artist]
&mdash;
= track[:name]
%td.song-duration
.s-duration= (track[:duration] != '0:00' ? track[:duration] : '&nbsp;'.html_safe)
%span.label.success.s-add{ 'data-id' => track[:id] }= t('player.add_one')

View File

@ -67,3 +67,5 @@ en:
thumbs_down: "This is not the song I was looking for or the quality of sound is bad" thumbs_down: "This is not the song I was looking for or the quality of sound is bad"
set_playlist: "Play" set_playlist: "Play"
playlists: "Playlists" playlists: "Playlists"
play: "Play"
play_all: "Play All"

View File

@ -67,3 +67,5 @@ ru:
thumbs_down: "Это не та песня или качество звука плохое" thumbs_down: "Это не та песня или качество звука плохое"
set_playlist: "Играть" set_playlist: "Играть"
playlists: "Плей-листы" playlists: "Плей-листы"
play: "Играть"
play_all: "Добавить все"

View File

@ -0,0 +1,5 @@
class AddPicUrlToPlaylists < ActiveRecord::Migration
def change
add_column :playlists, :pic_url, :string
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended to check this file into your version control system. # It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20111126142207) do ActiveRecord::Schema.define(:version => 20111127102836) do
create_table "album_formats", :force => true do |t| create_table "album_formats", :force => true do |t|
t.integer "album_id" t.integer "album_id"
@ -156,6 +156,7 @@ ActiveRecord::Schema.define(:version => 20111126142207) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "artist_id" t.integer "artist_id"
t.string "pic_url"
end end
create_table "release_formats", :force => true do |t| create_table "release_formats", :force => true do |t|