Fixed album/track controllers

This commit is contained in:
nelsonlaquet 2013-07-29 23:53:57 -05:00
parent d9d44f9e6e
commit d4789ebda3
22 changed files with 569 additions and 486 deletions

View file

@ -61,6 +61,6 @@
$this->_album->syncTrackIds($trackIds); $this->_album->syncTrackIds($trackIds);
$this->_album->save(); $this->_album->save();
return CommandResponse::succeed(); return CommandResponse::succeed(['real_cover_url' => $this->_album->getCoverUrl(Image::NORMAL)]);
} }
} }

View file

@ -107,7 +107,7 @@
$track->updateTags(); $track->updateTags();
$track->save(); $track->save();
return CommandResponse::succeed(); return CommandResponse::succeed(['real_cover_url' => $track->getCoverUrl(Image::NORMAL)]);
} }
private function removeTrackFromAlbum($track) { private function removeTrackFromAlbum($track) {

View file

@ -88,9 +88,9 @@
</ul> </ul>
</nav> </nav>
</section> </section>
<section ui-view class="site-content"> <ui-view class="site-content">
@yield('app_content') @yield('app_content')
</section> </ui-view>
</div> </div>
<ng-include src="'templates/partials/upload-dialog.html'" /> <ng-include src="'templates/partials/upload-dialog.html'" />

View file

@ -1,6 +1,10 @@
window.pfm.preloaders = {}
angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'], [ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'], [
'$routeProvider', '$locationProvider', '$stateProvider', '$dialogProvider' '$routeProvider', '$locationProvider', '$stateProvider', '$dialogProvider', '$injector'
(route, location, state, $dialogProvider) -> (route, location, state, $dialogProvider, $injector) ->
service = (name) -> angular.element(document.body).injector().get name
# Account # Account
@ -8,88 +12,58 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
url: '/account' url: '/account'
templateUrl: '/templates/account/settings.html' templateUrl: '/templates/account/settings.html'
controller: 'account-settings' controller: 'account-settings'
navigation:
index: 9
state.state 'account-content', state.state 'account-content',
url: '/account' url: '/account'
abstract: true abstract: true
templateUrl: '/templates/account/content/_layout.html' templateUrl: '/templates/account/content/_layout.html'
navigation:
index: 8
state.state 'account-content.tracks', state.state 'account-content.tracks',
url: '/tracks' url: '/tracks'
templateUrl: '/templates/account/content/tracks.html' templateUrl: '/templates/account/content/tracks.html'
controller: 'account-tracks' controller: 'account-tracks'
navigation:
index: 8
subIndex: 1
state.state 'account-content.tracks.edit', state.state 'account-content.tracks.edit',
url: '/edit/:track_id' url: '/edit/:track_id'
navigation: templateUrl: '/templates/account/content/track.html'
index: 8 controller: 'account-tracks-edit'
subIndex: 1
state.state 'account-content.albums', state.state 'account-content.albums',
url: '/albums' url: '/albums'
templateUrl: '/templates/account/content/albums.html' templateUrl: '/templates/account/content/albums.html'
controller: 'account-albums' controller: 'account-albums'
navigation:
index: 8
subIndex: 2
state.state 'account-content.albums.create', state.state 'account-content.albums.create',
url: '/create' url: '/create'
templateUrl: '/templates/account/content/album.html' templateUrl: '/templates/account/content/album.html'
controller: 'account-albums-edit' controller: 'account-albums-edit'
navigation:
index: 8
subIndex: 2
state.state 'account-content.albums.edit', state.state 'account-content.albums.edit',
url: '/edit/:album_id' url: '/edit/:album_id'
templateUrl: '/templates/account/content/album.html' templateUrl: '/templates/account/content/album.html'
controller: 'account-albums-edit' controller: 'account-albums-edit'
navigation:
index: 8
subIndex: 2
state.state 'account-content-playlists', state.state 'account-content-playlists',
url: '/account/playlists' url: '/account/playlists'
templateUrl: '/templates/account/content/playlists.html' templateUrl: '/templates/account/content/playlists.html'
controller: 'account-playlists' controller: 'account-playlists'
navigation:
index: 6
state.state 'account-favourites', state.state 'account-favourites',
url: '/account/favourites' url: '/account/favourites'
abstract: true abstract: true
templateUrl: '/templates/account/favourites/_layout.html' templateUrl: '/templates/account/favourites/_layout.html'
navigation:
index: 7
state.state 'account-favourites.tracks', state.state 'account-favourites.tracks',
url: '' url: ''
templateUrl: '/templates/account/favourites/tracks.html' templateUrl: '/templates/account/favourites/tracks.html'
navigation:
index: 7
subIndex: 1
state.state 'account-favourites.playlists', state.state 'account-favourites.playlists',
url: '/playlists' url: '/playlists'
templateUrl: '/templates/account/favourites/playlists.html' templateUrl: '/templates/account/favourites/playlists.html'
navigation:
index: 7
subIndex: 3
state.state 'account-favourites.albums', state.state 'account-favourites.albums',
url: '/albums' url: '/albums'
templateUrl: '/templates/account/favourites/albums.html' templateUrl: '/templates/account/favourites/albums.html'
navigation:
index: 7
subIndex: 2
# Tracks # Tracks
@ -97,53 +71,39 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
url: '/tracks' url: '/tracks'
templateUrl: '/templates/tracks/index.html' templateUrl: '/templates/tracks/index.html'
controller: 'tracks' controller: 'tracks'
navigation:
index: 2
# Albums # Albums
state.state 'albums', state.state 'albums',
url: '/albums' url: '/albums'
templateUrl: '/templates/albums/index.html' templateUrl: '/templates/albums/index.html'
navigation:
index: 3
# Playlists # Playlists
state.state 'playlists', state.state 'playlists',
url: '/playlists' url: '/playlists'
templateUrl: '/templates/playlists/index.html' templateUrl: '/templates/playlists/index.html'
navigation:
index: 4
state.state 'playlist', state.state 'playlist',
url: '/playlist/:id/:slug' url: '/playlist/:id/:slug'
templateUrl: '/templates/playlists/show.html' templateUrl: '/templates/playlists/show.html'
controller: 'playlist' controller: 'playlist'
navigation:
index: 4
# Artists # Artists
state.state 'artists', state.state 'artists',
url: '/artists' url: '/artists'
templateUrl: '/templates/artists/index.html' templateUrl: '/templates/artists/index.html'
navigation:
index: 5
# Pages # Pages
state.state 'faq', state.state 'faq',
url: '/faq' url: '/faq'
templateUrl: '/templates/pages/faq.html' templateUrl: '/templates/pages/faq.html'
navigation:
index: 11
state.state 'about', state.state 'about',
url: '/about' url: '/about'
templateUrl: '/templates/pages/about.html' templateUrl: '/templates/pages/about.html'
navigation:
index: 10
# Auth # Auth
@ -151,14 +111,10 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
url: '/login' url: '/login'
templateUrl: '/templates/auth/login.html' templateUrl: '/templates/auth/login.html'
controller: 'login' controller: 'login'
navigation:
index: 12
state.state 'register', state.state 'register',
url: '/register' url: '/register'
templateUrl: '/templates/auth/register.html' templateUrl: '/templates/auth/register.html'
navigation:
index: 13
# Hompage # Hompage
@ -167,14 +123,10 @@ angular.module 'ponyfm', ['ui.bootstrap', 'ui.state', 'ui.date', 'ui.sortable'],
url: '/' url: '/'
templateUrl: '/templates/dashboard.html' templateUrl: '/templates/dashboard.html'
controller: 'dashboard' controller: 'dashboard'
navigation:
index: 0
else else
state.state 'home', state.state 'home',
url: '/' url: '/'
templateUrl: '/templates/home/index.html' templateUrl: '/templates/home/index.html'
navigation:
index: 0
route.otherwise '/' route.otherwise '/'

View file

@ -1,6 +1,16 @@
window.pfm.preloaders['account-albums-edit'] = [
'account-tracks', 'account-albums', '$state'
(tracks, albums, $state) ->
defs = [tracks.refresh()]
if $state.params.album_id
defs.push albums.getEdit($state.params.album_id, true)
$.when.all defs
]
angular.module('ponyfm').controller "account-albums-edit", [ angular.module('ponyfm').controller "account-albums-edit", [
'$scope', '$state', 'taxonomies', '$dialog', 'lightbox' '$scope', '$state', '$dialog', 'account-albums'
($scope, $state, taxonomies, $dialog, lightbox) -> ($scope, $state, $dialog, albums) ->
$scope.isNew = $state.params.album_id == undefined $scope.isNew = $state.params.album_id == undefined
$scope.data.isEditorOpen = true $scope.data.isEditorOpen = true
$scope.errors = {} $scope.errors = {}
@ -38,33 +48,6 @@ angular.module('ponyfm').controller "account-albums-edit", [
$scope.isDirty = true $scope.isDirty = true
$scope.refresh = () ->
return if $scope.isNew
$.getJSON('/api/web/albums/edit/' + $state.params.album_id)
.done (album) -> $scope.$apply ->
$scope.isDirty = false
$scope.errors = {}
$scope.album =
id: album.id
title: album.title
description: album.description
remove_cover: false
cover: album.cover_url
$scope.tracks = []
$scope.tracks.push track for track in album.tracks
$scope.trackIds[track.id] = track for track in album.tracks
$scope.data.selectedAlbum.title = album.title
$scope.data.selectedAlbum.description = album.description
$scope.data.selectedAlbum.covers.normal = album.real_cover_url
if $scope.isNew
$scope.album =
title: ''
description: ''
else
$scope.refresh();
$scope.$on '$destroy', -> $scope.data.isEditorOpen = false $scope.$on '$destroy', -> $scope.data.isEditorOpen = false
$scope.saveAlbum = -> $scope.saveAlbum = ->
@ -92,7 +75,9 @@ angular.module('ponyfm').controller "account-albums-edit", [
$scope.$emit 'album-created' $scope.$emit 'album-created'
$state.transitionTo 'account-content.albums.edit', {album_id: response.id} $state.transitionTo 'account-content.albums.edit', {album_id: response.id}
else else
$scope.refresh() $scope.isDirty = false
$scope.data.selectedAlbum.title = $scope.album.title
$scope.data.selectedAlbum.covers.normal = response.real_cover_url
formData = new FormData() formData = new FormData()
@ -134,7 +119,29 @@ angular.module('ponyfm').controller "account-albums-edit", [
$scope.isDirty = true $scope.isDirty = true
$scope.$on '$stateChangeStart', (e) -> if !$scope.isNew
return if $scope.selectedTrack == null || !$scope.isDirty albums.getEdit($state.params.album_id).done (album) ->
$scope.album =
id: album.id
title: album.title
description: album.description
remove_cover: false
cover: album.cover_url
$scope.tracks = []
$scope.tracks.push track for track in album.tracks
$scope.trackIds[track.id] = track for track in album.tracks
else
$scope.album =
title: ''
description: ''
window.onbeforeunload = ->
return if !$scope.isDirty
"Are you sure you want to leave this page without saving your changes?"
$scope.$on '$locationChangeStart', (e) ->
return if !$scope.isDirty
e.preventDefault() if !confirm('Are you sure you want to leave this page without saving your changes?') e.preventDefault() if !confirm('Are you sure you want to leave this page without saving your changes?')
] ]

View file

@ -1,41 +1,36 @@
angular.module('ponyfm').controller "account-albums", [ window.pfm.preloaders['account-albums'] = [
'$scope', '$state', 'taxonomies', '$dialog', 'lightbox' 'account-tracks', 'account-albums'
($scope, $state, taxonomies, $dialog, lightbox) -> (tracks, albums) ->
albumsDb = {} $.when.all [tracks.refresh('published=true&in_album=false', true), albums.refresh(true)]
lastIndex = null ]
angular.module('ponyfm').controller "account-albums", [
'$scope', '$state', 'account-albums', 'account-tracks'
($scope, $state, albums, tracks) ->
$scope.albums = [] $scope.albums = []
$scope.data = $scope.data =
isEditorOpen: false isEditorOpen: false
selectedAlbum: null selectedAlbum: null
tracksDb: {} tracksDb: []
refreshTrackDatabase = () -> updateTracks = (tracks) ->
$.getJSON('/api/web/tracks/owned?published=true&in_album=false') $scope.data.tracksDb.push track for track in tracks
.done (tracks) -> $scope.$apply ->
$scope.data.tracksDb[track.id] = track for track in tracks
refreshList = () -> tracks.refresh('published=true&in_album=false').done updateTracks
$.getJSON('/api/web/albums/owned')
.done (albums) -> $scope.$apply ->
index = 0
album.index = index++ for album in albums
albumsDb[album.id] = album for album in albums
$scope.albums = albums
if $state.params.album_id != undefined albumsDb = {}
updateAlbums = (albums) ->
$scope.albums.length = 0
for album in albums
$scope.albums.push album
albumsDb[album.id] = album
if $state.params.album_id
selectAlbum albumsDb[$state.params.album_id] selectAlbum albumsDb[$state.params.album_id]
else if lastIndex != null
if $scope.albums.length
album = null
if $scope.albums.length > lastIndex + 1
album = $scope.albums[lastIndex]
else if lastIndex > 0
album = $scope.albums[lastIndex - 1]
else
album = $scope.albums[0]
$state.transitionTo 'account-content.albums.edit', {album_id: album.id} albums.refresh().done updateAlbums
selectAlbum = (album) -> $scope.data.selectedAlbum = album selectAlbum = (album) -> $scope.data.selectedAlbum = album
@ -45,15 +40,7 @@ angular.module('ponyfm').controller "account-albums", [
else else
selectAlbum null selectAlbum null
$scope.$on 'album-created', () -> refreshList() $scope.$on 'album-created', () -> albums.refresh(true).done(updateAlbums)
$scope.$on 'album-deleted', () -> albums.refresh(true).done(updateAlbums)
$scope.$on 'album-deleted', () -> $scope.$on 'album-updated', () -> tracks.refresh('published=true&in_album=false', true).done updateTracks
lastIndex = $scope.data.selectedAlbum.index
refreshList()
$scope.$on 'album-updated', () ->
refreshTrackDatabase()
refreshList()
refreshTrackDatabase()
] ]

View file

@ -1,13 +1,18 @@
window.pfm.preloaders['account-playlists'] = [
'playlists'
(playlists) -> playlists.refreshOwned true
]
angular.module('ponyfm').controller "account-playlists", [ angular.module('ponyfm').controller "account-playlists", [
'$scope', 'auth', '$dialog', 'playlists' '$scope', 'auth', '$dialog', 'playlists'
($scope, auth, $dialog, playlists) -> ($scope, auth, $dialog, playlists) ->
$scope.playlists = [] $scope.playlists = []
$scope.refresh = -> loadPlaylists = (playlists) ->
$.get('/api/web/playlists/owned')
.done (playlists) -> $scope.$apply ->
$scope.playlists.push playlist for playlist in playlists $scope.playlists.push playlist for playlist in playlists
playlists.refreshOwned().done loadPlaylists
$scope.editPlaylist = (playlist) -> $scope.editPlaylist = (playlist) ->
dialog = $dialog.dialog dialog = $dialog.dialog
templateUrl: '/templates/partials/playlist-dialog.html' templateUrl: '/templates/partials/playlist-dialog.html'
@ -36,6 +41,4 @@ angular.module('ponyfm').controller "account-playlists", [
content = $scope.playlists[index] content = $scope.playlists[index]
_.each playlist, (value, name) -> content[name] = value _.each playlist, (value, name) -> content[name] = value
$scope.playlists.sort (left, right) -> left.title.localeCompare right.title $scope.playlists.sort (left, right) -> left.title.localeCompare right.title
$scope.refresh();
] ]

View file

@ -0,0 +1,148 @@
window.pfm.preloaders['account-tracks-edit'] = [
'account-tracks', 'account-albums', 'taxonomies', '$state'
(tracks, albums, taxonomies, state) ->
$.when.all [albums.refresh(), taxonomies.refresh(), tracks.getEdit(state.params.track_id, true)]
]
angular.module('ponyfm').controller "account-tracks-edit", [
'$scope', '$state', 'taxonomies', '$dialog', 'account-albums', 'account-tracks'
($scope, $state, taxonomies, $dialog, albums, tracks) ->
$scope.isDirty = false
$scope.isSaving = false
$scope.taxonomies = taxonomies
$scope.selectedSongsTitle = 'None'
$scope.selectedSongs = {}
$scope.albums = []
$scope.selectedAlbum = null
albumsDb = {}
albums.refresh().done (albums) ->
$scope.albums.legnth = 0
albumsDb = {}
for album in albums
albumsDb[album.id] = album
$scope.albums.push album
$scope.selectAlbum = (album) ->
$scope.selectedAlbum = album
$scope.edit.album_id = if album then album.id else null
$scope.isDirty = true
$scope.setCover = (image, type) ->
delete $scope.edit.cover_id
delete $scope.edit.cover
if image == null
$scope.edit.remove_cover = true
else if type == 'file'
$scope.edit.cover = image
else if type == 'gallery'
$scope.edit.cover_id = image.id
$scope.isDirty = true
updateSongDisplay = () ->
if _.size $scope.selectedSongs
$scope.selectedSongsTitle = (_.map _.values($scope.selectedSongs), (s) -> s.title).join(', ')
else
$scope.selectedSongsTitle = 'None'
$scope.toggleSong = (song) ->
$scope.isDirty = true
if $scope.selectedSongs[song.id]
delete $scope.selectedSongs[song.id]
else
$scope.selectedSongs[song.id] = song
updateSongDisplay()
$scope.updateIsVocal = () ->
delete $scope.errors.lyrics if !$scope.edit.is_vocal
$scope.updateTrack = () ->
xhr = new XMLHttpRequest()
xhr.onload = -> $scope.$apply ->
$scope.isSaving = false
if xhr.status != 200
errors =
if xhr.getResponseHeader('content-type') == 'application/json'
$.parseJSON(xhr.responseText).errors
else
['There was an unknown error!']
$scope.errors = {}
_.each errors, (value, key) -> $scope.errors[key] = value.join ', '
return
track = $.parseJSON(xhr.responseText)
trackDbItem = $scope.data.selectedTrack
trackDbItem.title = $scope.edit.title
trackDbItem.is_explicit = $scope.edit.is_explicit
trackDbItem.is_vocal = $scope.edit.is_vocal
trackDbItem.genre_id = $scope.edit.genre_id
trackDbItem.is_published = true
trackDbItem.cover_url = track.real_cover_url
$scope.isDirty = false
$scope.errors = {}
formData = new FormData();
_.each $scope.edit, (value, name) ->
if name == 'cover'
return if value == null
if typeof(value) == 'object'
formData.append name, value, value.name
else
formData.append name, value
if $scope.edit.track_type_id == 2
formData.append 'show_song_ids', _.map(_.values($scope.selectedSongs), (s) -> s.id).join()
xhr.open 'POST', '/api/web/tracks/edit/' + $scope.edit.id, true
xhr.setRequestHeader 'X-Token', pfm.token
$scope.isSaving = true
xhr.send formData
tracks.getEdit($state.params.track_id).done (track) ->
$scope.edit =
id: track.id
title: track.title
description: track.description
lyrics: track.lyrics
is_explicit: track.is_explicit
is_downloadable: track.is_downloadable
is_vocal: track.is_vocal
license_id: track.license_id
genre_id: track.genre_id
track_type_id: track.track_type_id
released_at: if track.released_at then track.released_at.date else ''
remove_cover: false
cover: track.cover_url
album_id: track.album_id
is_published: track.is_published
$scope.selectedAlbum = if track.album_id then albumsDb[track.album_id] else null
$scope.selectedSongs = {}
$scope.selectedSongs[song.id] = song for song in track.show_songs
updateSongDisplay()
$scope.touchModel = -> $scope.isDirty = true
$scope.deleteTrack = (track) ->
$dialog.messageBox('Delete ' + track.title, 'Are you sure you want to delete "' + track.title + '"? This cannot be undone.', [
{result: 'ok', label: 'Yes', cssClass: 'btn-danger'}, {result: 'cancel', label: 'No', cssClass: 'btn-primary'}
]).open().then (res) ->
return if res == 'cancel'
$.post('/api/web/tracks/delete/' + track.id, {_token: window.pfm.token})
.then -> $scope.$apply ->
$scope.$emit 'track-deleted'
$state.transitionTo 'account-content.tracks'
window.onbeforeunload = ->
return if !$scope.isDirty
"Are you sure you want to leave this page without saving your changes?"
$scope.$on '$locationChangeStart', (e) ->
return if !$scope.isDirty
e.preventDefault() if !confirm('Are you sure you want to leave this page without saving your changes?')
]

View file

@ -1,95 +1,30 @@
window.pfm.preloaders['account-tracks'] = [
'account-tracks', 'account-albums', 'taxonomies'
(tracks, albums, taxonomies) ->
$.when.all [tracks.refresh(null, true), albums.refresh(true), taxonomies.refresh()]
]
angular.module('ponyfm').controller "account-tracks", [ angular.module('ponyfm').controller "account-tracks", [
'$scope', '$state', 'taxonomies', '$dialog', 'lightbox' '$scope', '$state', 'taxonomies', '$dialog', 'lightbox', 'account-albums', 'account-tracks'
($scope, $state, taxonomies, $dialog, lightbox) -> ($scope, $state, taxonomies, $dialog, lightbox, albums, tracks) ->
$scope.selectedTrack = null $scope.data =
$scope.isDirty = false selectedTrack: null
$scope.isSaving = false
$scope.taxonomies = taxonomies $scope.tracks = []
$scope.selectedSongsTitle = 'None'
$scope.selectedSongs = {}
$scope.albums = []
$scope.selectedAlbum = null
tracksDb = {} tracksDb = {}
albumsDb = {}
$scope.selectAlbum = (album) -> setTracks = (tracks) ->
$scope.selectedAlbum = album $scope.tracks.length = 0
$scope.edit.album_id = if album then album.id else null tracksDb = {}
$scope.isDirty = true for track in tracks
tracksDb[track.id] = track
$scope.tracks.push track
$scope.setCover = (image, type) -> if $state.params.track_id
delete $scope.edit.cover_id $scope.data.selectedTrack = tracksDb[$state.params.track_id]
delete $scope.edit.cover
if image == null tracks.refresh().done setTracks
$scope.edit.remove_cover = true
else if type == 'file'
$scope.edit.cover = image
else if type == 'gallery'
$scope.edit.cover_id = image.id
$scope.isDirty = true
refreshAlbums = () ->
$.getJSON('/api/web/albums/owned')
.done (albums) -> $scope.$apply ->
albumsDb[album.id] = album for album in albums
$scope.albums = albums
$scope.selectedAlbum = if $scope.edit && $scope.edit.album_id then albumsDb[$scope.edit.album_id] else null
updateSongDisplay = () ->
if _.size $scope.selectedSongs
$scope.selectedSongsTitle = (_.map _.values($scope.selectedSongs), (s) -> s.title).join(', ')
else
$scope.selectedSongsTitle = 'None'
$scope.toggleSong = (song) ->
$scope.isDirty = true
if $scope.selectedSongs[song.id]
delete $scope.selectedSongs[song.id]
else
$scope.selectedSongs[song.id] = song
updateSongDisplay()
$scope.updateIsVocal = () ->
delete $scope.errors.lyrics if !$scope.edit.is_vocal
$scope.updateTrack = (track) ->
xhr = new XMLHttpRequest()
xhr.onload = -> $scope.$apply ->
$scope.isSaving = false
if xhr.status != 200
errors =
if xhr.getResponseHeader('content-type') == 'application/json'
$.parseJSON(xhr.responseText).errors
else
['There was an unknown error!']
$scope.errors = {}
_.each errors, (value, key) -> $scope.errors[key] = value.join ', '
return
$scope.selectedTrack.is_published = true
selectTrack $scope.selectedTrack
formData = new FormData();
_.each $scope.edit, (value, name) ->
if name == 'cover'
return if value == null
if typeof(value) == 'object'
formData.append name, value, value.name
else
formData.append name, value
if $scope.edit.track_type_id == 2
formData.append 'show_song_ids', _.map(_.values($scope.selectedSongs), (s) -> s.id).join()
xhr.open 'POST', '/api/web/tracks/edit/' + $scope.edit.id, true
xhr.setRequestHeader 'X-Token', pfm.token
$scope.isSaving = true
xhr.send formData
$scope.filters = $scope.filters =
published: [ published: [
@ -114,12 +49,12 @@ angular.module('ponyfm').controller "account-tracks", [
genres: 'All' genres: 'All'
trackTypes: 'All' trackTypes: 'All'
taxonomies.refresh().done () ->
for genre in taxonomies.genres for genre in taxonomies.genres
$scope.filters.genres[genre.id] = $scope.filters.genres[genre.id] =
id: genre.id id: genre.id
title: genre.name title: genre.name
query: 'genres[]=' + genre.id query: 'genres[]=' + genre.id
for type in taxonomies.trackTypes for type in taxonomies.trackTypes
$scope.filters.trackTypes[type.id] = $scope.filters.trackTypes[type.id] =
id: type.id id: type.id
@ -151,76 +86,18 @@ angular.module('ponyfm').controller "account-tracks", [
_.each $scope.filter.genres, (g) -> parts.push g.query _.each $scope.filter.genres, (g) -> parts.push g.query
_.each $scope.filter.trackTypes, (g) -> parts.push g.query _.each $scope.filter.trackTypes, (g) -> parts.push g.query
query = parts.join '&' query = parts.join '&'
$.getJSON('/api/web/tracks/owned?' + query).done (tracks) -> $scope.$apply -> showTracks tracks tracks.refresh(query).done setTracks
showTracks = (tracks) -> $scope.selectTrack = (track) ->
tracksDb = {} $scope.data.selectedTrack = track
$scope.tracks = tracks
tracksDb[track.id] = track for track in tracks
selectTrack = (t) ->
$scope.selectedTrack = t
return if !t
$.getJSON('/api/web/tracks/edit/' + t.id)
.done (track) -> $scope.$apply ->
$scope.isDirty = false
$scope.errors = {}
$scope.edit =
id: track.id
title: track.title
description: track.description
lyrics: track.lyrics
is_explicit: track.is_explicit
is_downloadable: track.is_downloadable
is_vocal: track.is_vocal
license_id: track.license_id
genre_id: track.genre_id
track_type_id: track.track_type_id
released_at: if track.released_at then track.released_at.date else ''
remove_cover: false
cover: track.cover_url
album_id: track.album_id
trackDbItem = tracksDb[t.id]
trackDbItem.title = track.title
trackDbItem.is_explicit = track.is_explicit
trackDbItem.is_vocal = track.is_vocal
trackDbItem.genre_id = track.genre_id
trackDbItem.is_published = track.is_published
trackDbItem.cover_url = track.real_cover_url
$scope.selectedAlbum = if track.album_id then albumsDb[track.album_id] else null
$scope.selectedSongs = {}
$scope.selectedSongs[song.id] = song for song in track.show_songs
updateSongDisplay()
$scope.touchModel = -> $scope.isDirty = true
$.getJSON('/api/web/tracks/owned?order=created_at,desc').done (tracks) -> $scope.$apply ->
showTracks tracks
if $state.params.track_id
selectTrack tracksDb[$state.params.track_id]
$scope.selectTrack = (track) -> $scope.selectedTrack = track
$scope.deleteTrack = (track) ->
$dialog.messageBox('Delete ' + track.title, 'Are you sure you want to delete "' + track.title + '"? This cannot be undone.', [
{result: 'ok', label: 'Yes', cssClass: 'btn-danger'}, {result: 'cancel', label: 'No', cssClass: 'btn-primary'}
]).open().then (res) ->
return if res == 'cancel'
selectTrack null if track == $scope.selectedTrack
$.post('/api/web/tracks/delete/' + track.id, {_token: window.pfm.token})
.then ->
$scope.refreshList()
$scope.$on '$stateChangeSuccess', () -> $scope.$on '$stateChangeSuccess', () ->
if $state.params.track_id if $state.params.track_id
selectTrack tracksDb[$state.params.track_id] $scope.selectTrack tracksDb[$state.params.track_id]
else else
selectTrack null $scope.selectTrack null
$scope.$on '$stateChangeStart', (e) -> $scope.$on 'track-deleted', () ->
return if $scope.selectedTrack == null || !$scope.isDirty tracks.clearCache()
e.preventDefault() if !confirm('Are you sure you want to leave this page without saving your changes?') $scope.refreshList()
refreshAlbums()
] ]

View file

@ -1,52 +1,51 @@
angular.module('ponyfm').controller "application", [ angular.module('ponyfm').controller "application", [
'$scope', 'auth', '$location', 'upload', '$state', '$stateParams', 'taxonomies' '$scope', 'auth', '$location', 'upload', '$state', '$stateParams', '$injector'
($scope, auth, $location, upload, $state, $stateParams, taxonomies) -> ($scope, auth, $location, upload, $state, $stateParams, $injector) ->
$scope.auth = auth.data $scope.auth = auth.data
$scope.$state = $state $scope.$state = $state
$scope.$stateParams = $stateParams $scope.$stateParams = $stateParams
$loadingElement = null
$scope.logout = () -> $scope.logout = () ->
auth.logout().done -> location.reload() auth.logout().done -> location.reload()
$scope.isActive = (loc) -> $location.path() == loc $scope.isActive = (loc) -> $location.path() == loc
$scope.$on '$viewContentLoaded', () -> $scope.$on '$viewContentLoaded', () ->
window.setTimeout window.handleResize, 500 window.handleResize()
# Show loading screen here? if $loadingElement
taxonomies.refresh() $loadingElement.removeClass 'loading'
$loadingElement = null
$scope.mainViewAnimation = 'slide-down';
statesPreloaded = {}
$scope.$on '$stateChangeStart', (e, newState, newParams, oldState) -> $scope.$on '$stateChangeStart', (e, newState, newParams, oldState) ->
oldIndex = return if !oldState || !newState.controller
if (oldState && oldState.navigation && oldState.navigation.index)
oldState.navigation.index
else
0
newIndex = preloader = window.pfm.preloaders[newState.controller]
if (newState && newState.navigation && newState.navigation.index) return if !preloader
newState.navigation.index
else
0
oldSubIndex = if statesPreloaded[newState]
if (oldState && oldState.navigation && oldState.navigation.subIndex) delete statesPreloaded[newState]
oldState.navigation.subIndex return
else
0
newSubIndex = e.preventDefault()
if (newState && newState.navigation && newState.navigation.subIndex)
newState.navigation.subIndex
else
0
$scope.mainViewAnimation = 'slide-down' if oldIndex > newIndex selector = ''
$scope.mainViewAnimation = 'slide-up' if oldIndex < newIndex newParts = newState.name.split '.'
$scope.mainViewAnimation = 'slide-right' if oldIndex == newIndex oldParts = oldState.name.split '.'
zipped = _.zip(newParts, oldParts)
for i in [0..zipped.length]
break if !zipped[i] || zipped[i][0] != zipped[i][1]
selector += ' ui-view '
$scope.subViewAnimation = 'slide-right' if oldSubIndex > newSubIndex selector += ' ui-view ' if newState.name != oldState.name
$scope.subViewAnimation = 'slide-left' if oldSubIndex < newSubIndex
$scope.subViewAnimation = 'slide-up' if oldSubIndex == newSubIndex $loadingElement = $ selector
$loadingElement.addClass 'loading'
stateToInject = angular.copy newState
stateToInject.params = newParams
$injector.invoke(preloader, null, {$state: stateToInject}).then ->
statesPreloaded[newState] = true
$state.transitionTo newState, newParams
] ]

View file

@ -12,9 +12,6 @@ angular.module('ponyfm').directive 'pfmPopup', () ->
$positionParent = null $positionParent = null
open = false open = false
$popup.parents().each () ->
$this = $ this
$positionParent = $this if $positionParent == null && ($this.css('position') == 'relative' || $this.is 'body')
documentClickHandler = () -> documentClickHandler = () ->
return if !open return if !open
@ -22,6 +19,10 @@ angular.module('ponyfm').directive 'pfmPopup', () ->
open = false open = false
calculatePosition = -> calculatePosition = ->
$popup.parents().each () ->
$this = $ this
$positionParent = $this if $positionParent == null && ($this.css('position') == 'relative' || $this.is 'body')
position = $element.offset() position = $element.offset()
parentPosition = $positionParent.offset() parentPosition = $positionParent.offset()
@ -42,7 +43,7 @@ angular.module('ponyfm').directive 'pfmPopup', () ->
height = windowHeight - top; height = windowHeight - top;
return { return {
left: left - parentPosition.left - 2 left: left - parentPosition.left - 5
top: top - parentPosition.top, top: top - parentPosition.top,
height: height - 15} height: height - 15}
@ -70,6 +71,7 @@ angular.module('ponyfm').directive 'pfmPopup', () ->
$popup.addClass 'open' $popup.addClass 'open'
$popup.css 'height', 'auto' $popup.css 'height', 'auto'
window.setTimeout (->
position = calculatePosition() position = calculatePosition()
$popup.css $popup.css
left: position.left left: position.left
@ -77,6 +79,7 @@ angular.module('ponyfm').directive 'pfmPopup', () ->
height: position.height height: position.height
open = true open = true
), 0
scope.$on '$destroy', () -> scope.$on '$destroy', () ->
$(document.body).unbind 'click', documentClickHandler $(document.body).unbind 'click', documentClickHandler

View file

@ -0,0 +1,27 @@
angular.module('ponyfm').factory('account-albums', [
'$rootScope', '$http'
($rootScope, $http) ->
def = null
albums = []
self =
getEdit: (id, force) ->
url = '/api/web/albums/edit/' + id
force = force || false
return albums[id] if !force && albums[id]
editDef = new $.Deferred()
albums[id] = editDef
$http.get(url).success (album) -> editDef.resolve album
editDef.promise()
refresh: (force) ->
force = force || false
return def if !force && def
def = new $.Deferred()
$http.get('/api/web/albums/owned').success (ownedAlbums) ->
def.resolve(ownedAlbums)
def.promise()
self
])

View file

@ -0,0 +1,31 @@
angular.module('ponyfm').factory('account-tracks', [
'$rootScope', '$http'
($rootScope, $http) ->
cache = {}
self =
clearCache: () -> cache = {}
getEdit: (id, force) ->
url = '/api/web/tracks/edit/' + id
force = force || false
return cache[url] if !force && cache[url]
def = new $.Deferred()
cache[url] = def
$http.get(url).success (track) -> def.resolve track
def.promise()
refresh: (query, force) ->
query = query || 'created_at,desc'
url = '/api/web/tracks/owned?' + query
force = force || false
return cache[url] if !force && cache[url]
def = new $.Deferred()
cache[url] = def
$http.get(url).success (tracks) -> def.resolve tracks
def.promise()
self
])

View file

@ -1,8 +1,20 @@
angular.module('ponyfm').factory('playlists', [ angular.module('ponyfm').factory('playlists', [
'$rootScope', '$state' '$rootScope', '$state', '$http'
($rootScope, $state) -> ($rootScope, $state, $http) ->
playlistDef = null
self = self =
pinnedPlaylists: [] pinnedPlaylists: []
refreshOwned: (force) ->
force = force || false
return playlistDef if !force && playlistDef
playlistDef = new $.Deferred()
$http.get('/api/web/playlists/owned').success (playlists) ->
playlistDef.resolve playlists
playlistDef
refresh: () -> refresh: () ->
$.getJSON('/api/web/playlists/pinned') $.getJSON('/api/web/playlists/pinned')
.done (playlists) -> $rootScope.$apply -> .done (playlists) -> $rootScope.$apply ->

View file

@ -1,6 +1,6 @@
angular.module('ponyfm').factory('taxonomies', [ angular.module('ponyfm').factory('taxonomies', [
'$rootScope' '$rootScope', '$http'
($rootScope) -> ($rootScope, $http) ->
def = null def = null
self = self =
@ -9,17 +9,18 @@ angular.module('ponyfm').factory('taxonomies', [
genres: [] genres: []
showSongs: [] showSongs: []
refresh: () -> refresh: () ->
return def if def != null return def.promise() if def != null
def = new $.Deferred() def = new $.Deferred()
$.getJSON('/api/web/taxonomies/all') $http.get('/api/web/taxonomies/all')
.done (taxonomies) -> $rootScope.$apply -> .success (taxonomies) ->
self.trackTypes.push t for t in taxonomies.track_types self.trackTypes.push t for t in taxonomies.track_types
self.licenses.push t for t in taxonomies.licenses self.licenses.push t for t in taxonomies.licenses
self.genres.push t for t in taxonomies.genres self.genres.push t for t in taxonomies.genres
self.showSongs.push t for t in taxonomies.show_songs self.showSongs.push t for t in taxonomies.show_songs
def.resolve self def.resolve self
def
def.promise()
self self
]) ])

View file

@ -0,0 +1,14 @@
if (jQuery.when.all===undefined) {
jQuery.when.all = function(deferreds) {
var deferred = new jQuery.Deferred();
$.when.apply(jQuery, deferreds).then(
function() {
deferred.resolve(Array.prototype.slice.call(arguments));
},
function() {
deferred.fail(Array.prototype.slice.call(arguments));
});
return deferred;
}
}

View file

@ -2,18 +2,13 @@ window.handleResize = () ->
windowHeight = $(window).height() windowHeight = $(window).height()
$siteBody = $ '.site-body' $siteBody = $ '.site-body'
$siteBody.height windowHeight - $('header').height() - 1 $siteBody.height windowHeight - $('header').height() - 1
redo = false
$('.stretch-to-bottom').each () -> $('.stretch-to-bottom').each () ->
$this = $ this $this = $ this
newHeight = windowHeight - $this.offset().top + 1 newHeight = windowHeight - $this.offset().top + 1
if newHeight <= 0 if newHeight > 0
redo = true
else
$this.height newHeight $this.height newHeight
window.setTimeout(window.handleResize, 0) if redo
window.alignVertically = (element) -> window.alignVertically = (element) ->
$element = $(element) $element = $(element)
$parent = $element.parent() $parent = $element.parent()

View file

@ -21,6 +21,36 @@ html body {
z-index: -5; z-index: -5;
} }
ui-view {
display: block;
&:before {
.transition(opacity 250ms);
opacity: 0;
background: #fff;
content: ' ';
top: 0px;
left: 0px;
z-index: 1000;
width: 0px;
height: 0px;
position: absolute;
display: block;
overflow: hidden;
}
&.loading {
position: relative;
&:before {
opacity: .8;
width: 100%;
height: 100%;
}
}
}
header { header {
.clearfix(); .clearfix();
background: #222; background: #222;

View file

@ -4,5 +4,5 @@
<li ng-class="{active: $state.includes('account-content.albums')}"><a href="/account/albums">Albums</a></li> <li ng-class="{active: $state.includes('account-content.albums')}"><a href="/account/albums">Albums</a></li>
</ul> </ul>
<div ui-view class="inner-view"></div> <ui-view></ui-view>
</div> </div>

View file

@ -23,7 +23,6 @@
</ul> </ul>
</div> </div>
<div class="editor" ui-view> <ui-view class="editor"></ui-view>
</div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,116 @@
<form novalidate ng-submit="updateTrack(edit)">
<ul class="toolbar">
<li>
<button type="submit" class="btn" ng-class="{disabled: (data.selectedTrack.is_published && !isDirty) || isSaving, 'btn-primary': !data.selectedTrack.is_published || isDirty}">
<span ng-show="edit.is_published">
Save Changes
</span>
<span ng-hide="edit.is_published">
Publish Track
</span>
<i ng-show="isSaving" class="icon-cog icon-spin icon-large"></i>
</button>
</li>
<li class="delete"><a ng-class="{disabled: isSaving}" class="btn btn-danger" href="#" ng-click="deleteTrack(data.selectedTrack)" pfm-eat-click>Delete Track</a></li>
</ul>
<div class="stretch-to-bottom">
<div class="form-row" ng-class="{'has-error': errors.title != null}">
<label for="title" class="strong">Title:</label>
<input ng-disabled="isSaving" ng-change="touchModel()" placeholder="Track Title" type="text" id="title" ng-model="edit.title" />
<div class="error">{{errors.title}}</div>
</div>
<div class="row-fluid">
<div class="span6 form-row" ng-class="{'has-error': errors.description != null}">
<label for="description" class="strong">Description:</label>
<textarea ng-disabled="isSaving" ng-change="touchModel()" placeholder="Description (optional)" id="description" ng-model="edit.description"></textarea>
<div class="error">{{errors.description}}</div>
</div>
<div class="span6 form-row" ng-class="{'has-error': errors.lyrics != null}">
<label for="is_vocal" class="strong"><input ng-disabled="isSaving" ng-change="touchModel(); updateIsVocal()" id="is_vocal" type="checkbox" ng-model="edit.is_vocal" /> Is Vocal</label>
<textarea ng-disabled="isSaving" ng-change="touchModel()" ng-show="edit.is_vocal" ng-animate="'fade'" placeholder="Lyrics (required)" id="lyrics" ng-model="edit.lyrics"></textarea>
<div class="error">{{errors.lyrics}}</div>
</div>
</div>
<div class="row-fluid">
<div class="form-row span6" ng-class="{'has-error': errors.genre_id != null}">
<label for="genre" class="strong">Genre:</label>
<select ng-disabled="isSaving" id="genre" ng-change="touchModel()" ng-model="edit.genre_id" ng-options="genre.id as genre.name for genre in taxonomies.genres">
<option value="">Please select a genre...</option>
</select>
<div class="error">{{errors.genre_id}}</div>
</div>
<div class="form-row span6" ng-class="{'has-error': errors.track_type_id != null}">
<label for="track_type" class="strong">This track is...</label>
<select ng-disabled="isSaving" id="track_type" ng-change="touchModel()" ng-model="edit.track_type_id" ng-options="type.id as type.editor_title for type in taxonomies.trackTypes">
<option value="">Please select a type...</option>
</select>
<div class="error">{{errors.track_type_id}}</div>
</div>
</div>
<div class="row-fluid">
<div class="form-row album span6" ng-class="{'has-error': errors.show_song_ids != null}">
<a pfm-popup="album-selector" href="#" class="btn btn-small">
Album:
<strong ng-show="selectedAlbum">{{selectedAlbum.title}}</strong>
<strong ng-hide="selectedAlbum">None</strong>
</a>
<div id="album-selector" class="pfm-popup">
<ul>
<li ng-class="{selected: selectedAlbum == null}">
<a pfm-eat-click href="#" ng-click="selectAlbum(null);">None</a>
</li>
<li ng-repeat="album in albums" ng-class="{selected: selectedAlbum.id == album.id}">
<a pfm-eat-click href="#" ng-click="selectAlbum(album);">{{album.title}}</a>
</li>
</ul>
</div>
<div class="error">{{errors.album_id}}</div>
</div>
<div class="form-row show-songs span6" ng-show="edit.track_type_id == 2" ng-class="{'has-error': errors.show_song_ids != null}">
<a pfm-popup="song-selector" href="#" class="btn btn-small">Show Songs: <strong>{{selectedSongsTitle}}</strong></a>
<div id="song-selector" class="pfm-popup">
<ul>
<li ng-repeat="song in taxonomies.showSongs" ng-class="{selected: selectedSongs[song.id]}">
<a pfm-eat-click href="#" ng-click="toggleSong(song); $event.stopPropagation();">{{song.title}}</a>
</li>
</ul>
</div>
<div class="error">{{errors.show_song_ids}}</div>
</div>
</div>
<div class="row-fluid">
<div class="form-row span6" ng-class="{'has-error': errors.cover != null}">
<label class="strong">Track Cover: </label>
<pfm-image-upload set-image="setCover" image="edit.cover" />
</div>
<div class="form-row span6">
<label for="released_at" class="strong">Release Date:</label>
<input ng-disabled="isSaving" type="text" id="released_at" ui-date ng-model="edit.released_at" ng-change="touchModel()" ui-date-format="yy-mm-dd" />
<div class="error">{{errors.released_at}}</div>
</div>
</div>
<div class="row-fluid">
<div class="span6 form-row">
<label for="is_explicit"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_explicit" type="checkbox" ng-model="edit.is_explicit" /> Contains Explicit Content</label>
</div>
<div class="span6 form-row">
<label for="is_downloadable"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_downloadable" type="checkbox" ng-model="edit.is_downloadable" /> Is Downloadable</label>
</div>
</div>
<div class="form-row">
<label class="strong">Choose a License:</label>
<ul class="license-grid">
<li ng-repeat="license in taxonomies.licenses" ng-class="{selected: edit.license_id == license.id}">
<div ng-click="edit.license_id = license.id; touchModel()">
<strong>{{license.title}}</strong>
<p>{{license.description}}</p>
<a href="#" pfm-eat-click class="btn" ng-class="{'btn-primary': edit.license_id == license.id, 'disabled': isSaving}">
<span ng-hide="edit.license_id == license.id">Select</span>
<span ng-show="edit.license_id == license.id">Selected</span>
</a>
</div>
</li>
</ul>
</div>
</div>
</form>

View file

@ -25,7 +25,7 @@
Type: <strong>{{titles.trackTypes}}</strong> Type: <strong>{{titles.trackTypes}}</strong>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li ng-repeat="type in taxonomies.trackTypes" ng-class="{selected: filter.trackTypes[type.id]}"> <li ng-repeat="type in filters.trackTypes" ng-class="{selected: filter.trackTypes[type.id]}">
<a pfm-eat-click href="#" ng-click="toggleFilter('trackTypes', type.id); $event.stopPropagation();">{{type.title}}</a> <a pfm-eat-click href="#" ng-click="toggleFilter('trackTypes', type.id); $event.stopPropagation();">{{type.title}}</a>
</li> </li>
</ul> </ul>
@ -35,18 +35,18 @@
Genera: <strong>{{titles.genres}}</strong> Genera: <strong>{{titles.genres}}</strong>
</a> </a>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
<li ng-repeat="genre in taxonomies.genres" ng-class="{selected: filter.genres[genre.id]}"> <li ng-repeat="genre in filters.genres" ng-class="{selected: filter.genres[genre.id]}">
<a pfm-eat-click href="#" ng-click="toggleFilter('genres', genre.id); $event.stopPropagation();">{{genre.name}}</a> <a pfm-eat-click href="#" ng-click="toggleFilter('genres', genre.id); $event.stopPropagation();">{{genre.title}}</a>
</li> </li>
</ul> </ul>
</li> </li>
</ul> </ul>
<div class="two-pane-view" ng-class="{open: selectedTrack != null, closed: selectedTrack == null}"> <div class="two-pane-view" ng-class="{open: data.selectedTrack != null, closed: data.selectedTrack == null}">
<div class="list"> <div class="list">
<ul class="account-tracks-listing stretch-to-bottom"> <ul class="account-tracks-listing stretch-to-bottom">
<li ng-repeat="track in tracks" ng-class="{selected: track.id == selectedTrack.id, 'is-not-published': !track.is_published}"> <li ng-repeat="track in tracks" ng-class="{selected: track.id == data.selectedTrack.id, 'is-not-published': !track.is_published}">
<a href="/account/tracks/edit/{{track.id}}"> <a href="/account/tracks/edit/{{track.id}}" ng-click="selectTrack(track)">
<img class="image" ng-src="{{track.cover_url}}" /> <img class="image" ng-src="{{track.cover_url}}" />
<span class="title">{{track.title}}</span> <span class="title">{{track.title}}</span>
<span class="published">{{track.created_at | pfmdate:'MM/dd/yyyy'}}</span> <span class="published">{{track.created_at | pfmdate:'MM/dd/yyyy'}}</span>
@ -55,124 +55,6 @@
</ul> </ul>
</div> </div>
<div class="editor"> <ui-view class="editor"></ui-view>
<form novalidate ng-submit="updateTrack(edit)">
<ul class="toolbar">
<li>
<button type="submit" class="btn" ng-class="{disabled: (selectedTrack.is_published && !isDirty) || isSaving, 'btn-primary': !selectedTrack.is_published || isDirty}">
<span ng-show="selectedTrack.is_published">
Save Changes
</span>
<span ng-hide="selectedTrack.is_published">
Publish Track
</span>
<i ng-show="isSaving" class="icon-cog icon-spin icon-large"></i>
</button>
</li>
<li class="delete"><a ng-class="{disabled: isSaving}" class="btn btn-danger" href="#" ng-click="deleteTrack(selectedTrack)" pfm-eat-click>Delete Track</a></li>
</ul>
<div class="stretch-to-bottom">
<div class="form-row" ng-class="{'has-error': errors.title != null}">
<label for="title" class="strong">Title:</label>
<input ng-disabled="isSaving" ng-change="touchModel()" placeholder="Track Title" type="text" id="title" ng-model="edit.title" />
<div class="error">{{errors.title}}</div>
</div>
<div class="row-fluid">
<div class="span6 form-row" ng-class="{'has-error': errors.description != null}">
<label for="description" class="strong">Description:</label>
<textarea ng-disabled="isSaving" ng-change="touchModel()" placeholder="Description (optional)" id="description" ng-model="edit.description"></textarea>
<div class="error">{{errors.description}}</div>
</div>
<div class="span6 form-row" ng-class="{'has-error': errors.lyrics != null}">
<label for="is_vocal" class="strong"><input ng-disabled="isSaving" ng-change="touchModel(); updateIsVocal()" id="is_vocal" type="checkbox" ng-model="edit.is_vocal" /> Is Vocal</label>
<textarea ng-disabled="isSaving" ng-change="touchModel()" ng-show="edit.is_vocal" ng-animate="'fade'" placeholder="Lyrics (required)" id="lyrics" ng-model="edit.lyrics"></textarea>
<div class="error">{{errors.lyrics}}</div>
</div>
</div>
<div class="row-fluid">
<div class="form-row span6" ng-class="{'has-error': errors.genre_id != null}">
<label for="genre" class="strong">Genre:</label>
<select ng-disabled="isSaving" id="genre" ng-change="touchModel()" ng-model="edit.genre_id" ng-options="genre.id as genre.name for genre in taxonomies.genres">
<option value="">Please select a genre...</option>
</select>
<div class="error">{{errors.genre_id}}</div>
</div>
<div class="form-row span6" ng-class="{'has-error': errors.track_type_id != null}">
<label for="track_type" class="strong">This track is...</label>
<select ng-disabled="isSaving" id="track_type" ng-change="touchModel()" ng-model="edit.track_type_id" ng-options="type.id as type.editor_title for type in taxonomies.trackTypes">
<option value="">Please select a type...</option>
</select>
<div class="error">{{errors.track_type_id}}</div>
</div>
</div>
<div class="row-fluid">
<div class="form-row album span6" ng-class="{'has-error': errors.show_song_ids != null}">
<a pfm-popup="album-selector" href="#" class="btn btn-small">
Album:
<strong ng-show="selectedAlbum">{{selectedAlbum.title}}</strong>
<strong ng-hide="selectedAlbum">None</strong>
</a>
<div id="album-selector" class="pfm-popup">
<ul>
<li ng-class="{selected: selectedAlbum == null}">
<a pfm-eat-click href="#" ng-click="selectAlbum(null);">None</a>
</li>
<li ng-repeat="album in albums" ng-class="{selected: selectedAlbum.id == album.id}">
<a pfm-eat-click href="#" ng-click="selectAlbum(album);">{{album.title}}</a>
</li>
</ul>
</div>
<div class="error">{{errors.album_id}}</div>
</div>
<div class="form-row show-songs span6" ng-show="edit.track_type_id == 2" ng-class="{'has-error': errors.show_song_ids != null}">
<a pfm-popup="song-selector" href="#" class="btn btn-small">Show Songs: <strong>{{selectedSongsTitle}}</strong></a>
<div id="song-selector" class="pfm-popup">
<ul>
<li ng-repeat="song in taxonomies.showSongs" ng-class="{selected: selectedSongs[song.id]}">
<a pfm-eat-click href="#" ng-click="toggleSong(song); $event.stopPropagation();">{{song.title}}</a>
</li>
</ul>
</div>
<div class="error">{{errors.show_song_ids}}</div>
</div>
</div>
<div class="row-fluid">
<div class="form-row span6" ng-class="{'has-error': errors.cover != null}">
<label class="strong">Track Cover: </label>
<pfm-image-upload set-image="setCover" image="edit.cover" />
</div>
<div class="form-row span6">
<label for="released_at" class="strong">Release Date:</label>
<input ng-disabled="isSaving" type="text" id="released_at" ui-date ng-model="edit.released_at" ng-change="touchModel()" ui-date-format="yy-mm-dd" />
<div class="error">{{errors.released_at}}</div>
</div>
</div>
<div class="row-fluid">
<div class="span6 form-row">
<label for="is_explicit"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_explicit" type="checkbox" ng-model="edit.is_explicit" /> Contains Explicit Content</label>
</div>
<div class="span6 form-row">
<label for="is_downloadable"><input ng-disabled="isSaving" ng-change="touchModel()" id="is_downloadable" type="checkbox" ng-model="edit.is_downloadable" /> Is Downloadable</label>
</div>
</div>
<div class="form-row">
<label class="strong">Choose a License:</label>
<ul class="license-grid">
<li ng-repeat="license in taxonomies.licenses" ng-class="{selected: edit.license_id == license.id}">
<div ng-click="edit.license_id = license.id; touchModel()">
<strong>{{license.title}}</strong>
<p>{{license.description}}</p>
<a href="#" pfm-eat-click class="btn" ng-class="{'btn-primary': edit.license_id == license.id, 'disabled': isSaving}">
<span ng-hide="edit.license_id == license.id">Select</span>
<span ng-show="edit.license_id == license.id">Selected</span>
</a>
</div>
</li>
</ul>
</div>
</div>
</form>
</div>
</div> </div>
</div> </div>