mirror of
https://github.com/Poniverse/Pony.fm.git
synced 2024-11-22 04:58:01 +01:00
Logout
Volume Slider Track Uploader Fav list A bunch of other things And stuff.
This commit is contained in:
parent
1dff7cb36f
commit
18e41d2af4
58 changed files with 1013 additions and 359 deletions
|
@ -3,10 +3,96 @@
|
|||
namespace Api\Web;
|
||||
|
||||
use Commands\ToggleFavouriteCommand;
|
||||
use Entities\Album;
|
||||
use Entities\Favourite;
|
||||
use Entities\Playlist;
|
||||
use Entities\Track;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Input;
|
||||
use Illuminate\Support\Facades\Response;
|
||||
|
||||
class FavouritesController extends \ApiControllerBase {
|
||||
public function postToggle() {
|
||||
return $this->execute(new ToggleFavouriteCommand(Input::get('type'), Input::get('id')));
|
||||
}
|
||||
|
||||
public function getTracks() {
|
||||
$query = Favourite
|
||||
::whereUserId(Auth::user()->id)
|
||||
->whereNotNull('track_id')
|
||||
->with([
|
||||
'track' => function($query) {
|
||||
$query
|
||||
->userDetails()
|
||||
->published();
|
||||
},
|
||||
'track.user',
|
||||
'track.genre',
|
||||
'track.cover',
|
||||
'track.album',
|
||||
'track.album.user'
|
||||
]);
|
||||
|
||||
$tracks = [];
|
||||
|
||||
foreach ($query->get() as $fav) {
|
||||
if ($fav->track == null) // deleted track
|
||||
continue;
|
||||
|
||||
$tracks[] = Track::mapPublicTrackSummary($fav->track);
|
||||
}
|
||||
|
||||
return Response::json(["tracks" => $tracks], 200);
|
||||
}
|
||||
|
||||
public function getAlbums() {
|
||||
$query = Favourite
|
||||
::whereUserId(Auth::user()->id)
|
||||
->whereNotNull('album_id')
|
||||
->with([
|
||||
'album' => function($query) {
|
||||
$query->userDetails();
|
||||
},
|
||||
'album.user',
|
||||
'album.user.avatar',
|
||||
'album.cover'
|
||||
]);
|
||||
|
||||
$albums = [];
|
||||
|
||||
foreach ($query->get() as $fav) {
|
||||
if ($fav->album == null) // deleted album
|
||||
continue;
|
||||
|
||||
$albums[] = Album::mapPublicAlbumSummary($fav->album);
|
||||
}
|
||||
|
||||
return Response::json(["albums" => $albums], 200);
|
||||
}
|
||||
|
||||
public function getPlaylists() {
|
||||
$query = Favourite
|
||||
::whereUserId(Auth::user()->id)
|
||||
->whereNotNull('playlist_id')
|
||||
->with([
|
||||
'playlist' => function($query) {
|
||||
$query->userDetails();
|
||||
},
|
||||
'playlist.user',
|
||||
'playlist.user.avatar',
|
||||
'playlist.tracks',
|
||||
'playlist.tracks.cover'
|
||||
]);
|
||||
|
||||
$playlists = [];
|
||||
|
||||
foreach ($query->get() as $fav) {
|
||||
if ($fav->playlist == null) // deleted playlist
|
||||
continue;
|
||||
|
||||
$playlists[] = Playlist::mapPublicPlaylistSummary($fav->playlist);
|
||||
}
|
||||
|
||||
return Response::json(["playlists" => $playlists], 200);
|
||||
}
|
||||
}
|
7
app/controllers/UploaderController.php
Normal file
7
app/controllers/UploaderController.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
class UploaderController extends Controller {
|
||||
public function getIndex() {
|
||||
return View::make('shared.null');
|
||||
}
|
||||
}
|
|
@ -35,12 +35,11 @@
|
|||
* @return CommandResponse
|
||||
*/
|
||||
public function execute() {
|
||||
$isVocal = isset($this->_input['is_vocal']) && $this->_input['is_vocal'] == 'true' ? true : false;
|
||||
$isVocal = (isset($this->_input['is_vocal']) && $this->_input['is_vocal'] == 'true') ? true : false;
|
||||
|
||||
$rules = [
|
||||
'title' => 'required|min:3|max:80',
|
||||
'released_at' => 'before:today' . ($this->_input['released_at'] != "" ? '|date' : ''),
|
||||
'lyrics' => $isVocal ? 'required' : '',
|
||||
'license_id' => 'required|exists:licenses,id',
|
||||
'genre_id' => 'required|exists:genres,id',
|
||||
'cover' => 'image|mimes:png|min_width:350|min_height:350',
|
||||
|
@ -50,6 +49,9 @@
|
|||
'album_id' => 'exists:albums,id'
|
||||
];
|
||||
|
||||
if ($isVocal)
|
||||
$rules['lyrics'] = 'required';
|
||||
|
||||
if ($this->_input['track_type_id'] == 2)
|
||||
$rules['show_song_ids'] = 'required|exists:show_songs,id';
|
||||
|
||||
|
@ -80,7 +82,7 @@
|
|||
$track->album_id = $this->_input['album_id'];
|
||||
|
||||
Album::whereId($album->id)->update([
|
||||
'track_count' => DB::raw('SELECT COUNT(id) FROM tracks WHERE album_id = ' . $album->id)
|
||||
'track_count' => DB::raw('(SELECT COUNT(id) FROM tracks WHERE album_id = ' . $album->id . ')')
|
||||
]);
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -132,7 +132,11 @@
|
|||
'name' => $album->user->display_name,
|
||||
'url' => $album->user->url,
|
||||
],
|
||||
'user_data' => $userData
|
||||
'user_data' => $userData,
|
||||
'permissions' => [
|
||||
'delete' => Auth::check() && Auth::user()->id == $album->user_id,
|
||||
'edit' => Auth::check() && Auth::user()->id == $album->user_id
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -105,7 +105,11 @@
|
|||
'name' => $playlist->user->display_name,
|
||||
'url' => $playlist->user->url,
|
||||
],
|
||||
'user_data' => $userData
|
||||
'user_data' => $userData,
|
||||
'permissions' => [
|
||||
'delete' => Auth::check() && Auth::user()->id == $playlist->user_id,
|
||||
'edit' => Auth::check() && Auth::user()->id == $playlist->user_id
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -38,8 +38,6 @@
|
|||
$query->whereUserId(Auth::user()->id);
|
||||
}]);
|
||||
}
|
||||
|
||||
return !$query;
|
||||
}
|
||||
|
||||
public function scopePublished($query) {
|
||||
|
@ -194,7 +192,11 @@
|
|||
'streams' => [
|
||||
'mp3' => $track->getStreamUrl('MP3')
|
||||
],
|
||||
'user_data' => $userData
|
||||
'user_data' => $userData,
|
||||
'permissions' => [
|
||||
'delete' => Auth::check() && Auth::user()->id == $track->user_id,
|
||||
'edit' => Auth::check() && Auth::user()->id == $track->user_id
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -106,6 +106,10 @@
|
|||
|
||||
Route::get('/playlists/owned', 'Api\Web\PlaylistsController@getOwned');
|
||||
Route::get('/playlists/pinned', 'Api\Web\PlaylistsController@getPinned');
|
||||
|
||||
Route::get('/favourites/tracks', 'Api\Web\FavouritesController@getTracks');
|
||||
Route::get('/favourites/albums', 'Api\Web\FavouritesController@getAlbums');
|
||||
Route::get('/favourites/playlists', 'Api\Web\FavouritesController@getPlaylists');
|
||||
});
|
||||
|
||||
Route::group(['before' => 'csrf'], function(){
|
||||
|
@ -127,6 +131,8 @@
|
|||
Route::get('/albums/create', 'ContentController@getAlbums');
|
||||
Route::get('/playlists', 'ContentController@getPlaylists');
|
||||
|
||||
Route::get('/uploader', 'UploaderController@getIndex');
|
||||
|
||||
Route::get('/', 'AccountController@getIndex');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,18 @@
|
|||
<header>
|
||||
<a href="/">Pony.fm</a>
|
||||
<div class="now-playing">
|
||||
@if (Auth::check())
|
||||
<div class="user-details dropdown">
|
||||
<a class="avatar dropdown-toggle" href="#">
|
||||
<img src="{{Auth::user()->getAvatarUrl(\Entities\Image::THUMBNAIL)}}" />
|
||||
<span><i class="icon-chevron-down"></i></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{{Auth::user()->url}}">Your Profile</a></li>
|
||||
<li><a href="#" pfm-eat-click ng-click="logout()">Logout</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
<pfm-player></pfm-player>
|
||||
</div>
|
||||
</header>
|
||||
|
@ -42,29 +54,25 @@
|
|||
</li>
|
||||
|
||||
@if (Auth::check())
|
||||
<li ng-class="{selected: stateIncludes('account-content') || isActive('/account')}"><a href="/account/tracks">Account <i class="icon-user"></i></a></li>
|
||||
<li ng-class="{selected: stateIncludes('account')}"><a href="/account/tracks">Account <i class="icon-user"></i></a></li>
|
||||
<li ng-class="{selected: stateIncludes('favourites')}"><a href="/account/favourites/tracks">Favourites <i class="icon-star"></i></a></li>
|
||||
@endif
|
||||
|
||||
<li ng-class="{selected: isActive('/about')}"><a href="/about">Meta <i class="icon-info"></i></a></li>
|
||||
<li ng-class="{selected: isActive('/about')}"><a href="/about">About <i class="icon-info"></i></a></li>
|
||||
|
||||
@if (Auth::check())
|
||||
<li class="uploader" ng-class="{selected: stateIncludes('uploader')}">
|
||||
<a href="/account/uploader">Upload Music <i class="icon-upload-alt"></i></a>
|
||||
</li>
|
||||
<li>
|
||||
<h3>
|
||||
<a href="#" ng-click="createPlaylist()" pfm-eat-click title="Create Playlist"><i class="icon-plus"></i></a>
|
||||
<a href="/account/playlists" ng-class="{selected: $state.is('account-content-playlists')}" title="View Playlists" class="view-all"><i class="icon-list"></i></a>
|
||||
Playlists
|
||||
</h3>
|
||||
</li>
|
||||
<li class="none" ng-show="!playlists.length"><span>no pinned playlists</span></li>
|
||||
<li class="dropdown" ng-repeat="playlist in playlists" ng-cloak ng-class="{selected: stateIncludes('content.playlist') && $state.params.id == playlist.id}">
|
||||
<a class="menu dropdown-toggle" pfm-eat-click href="#"><i class="icon-ellipsis-vertical"></i></a>
|
||||
<a href="{{Helpers::angular('playlist.url')}}" ng-bind="playlist.title"></a>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="#" pfm-eat-click ng-click="editPlaylist(playlist)">Edit</a></li>
|
||||
<li><a href="#" pfm-eat-click ng-click="unpinPlaylist(playlist)">Unpin</a></li>
|
||||
<li><a href="#" pfm-eat-click ng-click="deletePlaylist(playlist)" ng-show="playlist.user_id == auth.user_id">Delete</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
@endif
|
||||
</ul>
|
||||
|
@ -73,8 +81,6 @@
|
|||
</ui-view>
|
||||
</div>
|
||||
|
||||
<ng-include src="'templates/partials/upload-dialog.html'"></ng-include>
|
||||
|
||||
@endsection
|
||||
|
||||
@section('styles')
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
@yield('styles')
|
||||
</head>
|
||||
<body ng-app="ponyfm" ng-controller="application" uploader>
|
||||
<body ng-app="ponyfm" ng-controller="application">
|
||||
@yield('content')
|
||||
@yield('scripts')
|
||||
</body>
|
||||
|
|
|
@ -6,64 +6,74 @@ module.config [
|
|||
'$locationProvider', '$stateProvider', '$dialogProvider'
|
||||
(location, state, $dialogProvider) ->
|
||||
|
||||
# Upload
|
||||
|
||||
state.state 'uploader',
|
||||
url: '/account/uploader'
|
||||
templateUrl: '/templates/uploader/index.html'
|
||||
controller: 'uploader'
|
||||
|
||||
# Account
|
||||
|
||||
state.state 'account',
|
||||
url: '/account'
|
||||
abstract: true
|
||||
templateUrl: '/templates/account/_layout.html'
|
||||
|
||||
state.state 'account.settings',
|
||||
url: ''
|
||||
templateUrl: '/templates/account/settings.html'
|
||||
controller: 'account-settings'
|
||||
|
||||
state.state 'account-content',
|
||||
url: '/account'
|
||||
abstract: true
|
||||
templateUrl: '/templates/account/content/_layout.html'
|
||||
|
||||
state.state 'account-content.tracks',
|
||||
state.state 'account.tracks',
|
||||
url: '/tracks'
|
||||
templateUrl: '/templates/account/content/tracks.html'
|
||||
templateUrl: '/templates/account/tracks.html'
|
||||
controller: 'account-tracks'
|
||||
|
||||
state.state 'account-content.tracks.edit',
|
||||
state.state 'account.tracks.edit',
|
||||
url: '/edit/:track_id'
|
||||
templateUrl: '/templates/account/content/track.html'
|
||||
controller: 'account-tracks-edit'
|
||||
templateUrl: '/templates/account/track.html'
|
||||
controller: 'account-track'
|
||||
|
||||
state.state 'account-content.albums',
|
||||
state.state 'account.albums',
|
||||
url: '/albums'
|
||||
templateUrl: '/templates/account/content/albums.html'
|
||||
templateUrl: '/templates/account/albums.html'
|
||||
controller: 'account-albums'
|
||||
|
||||
state.state 'account-content.albums.create',
|
||||
state.state 'account.albums.create',
|
||||
url: '/create'
|
||||
templateUrl: '/templates/account/content/album.html'
|
||||
templateUrl: '/templates/account/album.html'
|
||||
controller: 'account-albums-edit'
|
||||
|
||||
state.state 'account-content.albums.edit',
|
||||
state.state 'account.albums.edit',
|
||||
url: '/edit/:album_id'
|
||||
templateUrl: '/templates/account/content/album.html'
|
||||
templateUrl: '/templates/account/album.html'
|
||||
controller: 'account-albums-edit'
|
||||
|
||||
state.state 'account-content-playlists',
|
||||
url: '/account/playlists'
|
||||
templateUrl: '/templates/account/content/playlists.html'
|
||||
state.state 'account.playlists',
|
||||
url: '/playlists'
|
||||
templateUrl: '/templates/account/playlists.html'
|
||||
controller: 'account-playlists'
|
||||
|
||||
state.state 'account-favourites',
|
||||
state.state 'favourites',
|
||||
url: '/account/favourites'
|
||||
abstract: true
|
||||
templateUrl: '/templates/account/favourites/_layout.html'
|
||||
templateUrl: '/templates/favourites/_layout.html'
|
||||
|
||||
state.state 'account-favourites.tracks',
|
||||
url: ''
|
||||
templateUrl: '/templates/account/favourites/tracks.html'
|
||||
state.state 'favourites.tracks',
|
||||
url: '/tracks'
|
||||
templateUrl: '/templates/favourites/tracks.html'
|
||||
controller: 'favourites-tracks'
|
||||
|
||||
state.state 'account-favourites.playlists',
|
||||
state.state 'favourites.playlists',
|
||||
url: '/playlists'
|
||||
templateUrl: '/templates/account/favourites/playlists.html'
|
||||
templateUrl: '/templates/favourites/playlists.html'
|
||||
controller: 'favourites-playlists'
|
||||
|
||||
state.state 'account-favourites.albums',
|
||||
state.state 'favourites.albums',
|
||||
url: '/albums'
|
||||
templateUrl: '/templates/account/favourites/albums.html'
|
||||
templateUrl: '/templates/favourites/albums.html'
|
||||
controller: 'favourites-albums'
|
||||
|
||||
# Tracks
|
||||
|
||||
|
@ -72,14 +82,14 @@ module.config [
|
|||
templateUrl: '/templates/content/_layout.html'
|
||||
|
||||
state.state 'content.tracks',
|
||||
templateUrl: '/templates/tracks/search.html'
|
||||
templateUrl: '/templates/tracks/index.html'
|
||||
controller: 'tracks'
|
||||
url: '/tracks'
|
||||
abstract: true
|
||||
|
||||
state.state 'content.tracks.list',
|
||||
url: '^/tracks?filter&page'
|
||||
templateUrl: '/templates/tracks/search-list.html'
|
||||
templateUrl: '/templates/tracks/list.html'
|
||||
controller: 'tracks-list'
|
||||
|
||||
state.state 'content.track',
|
||||
|
|
|
@ -73,7 +73,7 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
|||
if $scope.isNew
|
||||
$scope.isDirty = false
|
||||
$scope.$emit 'album-created'
|
||||
$state.transitionTo 'account-content.albums.edit', {album_id: response.id}
|
||||
$state.transitionTo 'account.albums.edit', {album_id: response.id}
|
||||
else
|
||||
$scope.isDirty = false
|
||||
$scope.data.selectedAlbum.title = $scope.album.title
|
||||
|
@ -104,7 +104,7 @@ angular.module('ponyfm').controller "account-albums-edit", [
|
|||
$.post('/api/web/albums/delete/' + $scope.album.id, {_token: window.pfm.token})
|
||||
.then -> $scope.$apply ->
|
||||
$scope.$emit 'album-deleted'
|
||||
$state.transitionTo 'account-content.albums'
|
||||
$state.transitionTo 'account.albums'
|
||||
|
||||
$scope.setCover = (image, type) ->
|
||||
delete $scope.album.cover_id
|
||||
|
|
|
@ -13,6 +13,8 @@ angular.module('ponyfm').controller "account-albums", [
|
|||
selectedAlbum: null
|
||||
tracksDb: []
|
||||
|
||||
selectAlbum = (album) -> $scope.data.selectedAlbum = album
|
||||
|
||||
updateTracks = (tracks) ->
|
||||
$scope.data.tracksDb.push track for track in tracks
|
||||
|
||||
|
@ -32,8 +34,6 @@ angular.module('ponyfm').controller "account-albums", [
|
|||
|
||||
albums.refresh().done updateAlbums
|
||||
|
||||
selectAlbum = (album) -> $scope.data.selectedAlbum = album
|
||||
|
||||
$scope.$on '$stateChangeSuccess', () ->
|
||||
if $state.params.album_id
|
||||
selectAlbum albumsDb[$state.params.album_id]
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
window.pfm.preloaders['account-tracks-edit'] = [
|
||||
window.pfm.preloaders['account-track'] = [
|
||||
'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) ->
|
||||
angular.module('ponyfm').controller "account-track", [
|
||||
'$scope', '$state', 'taxonomies', '$dialog', 'account-albums', 'account-tracks', 'images'
|
||||
($scope, $state, taxonomies, $dialog, albums, tracks, images) ->
|
||||
$scope.isDirty = false
|
||||
$scope.isSaving = false
|
||||
$scope.taxonomies = taxonomies
|
||||
|
@ -85,6 +85,7 @@ angular.module('ponyfm').controller "account-tracks-edit", [
|
|||
trackDbItem.cover_url = track.real_cover_url
|
||||
$scope.isDirty = false
|
||||
$scope.errors = {}
|
||||
images.refresh true
|
||||
|
||||
formData = new FormData();
|
||||
_.each $scope.edit, (value, name) ->
|
||||
|
@ -136,7 +137,7 @@ angular.module('ponyfm').controller "account-tracks-edit", [
|
|||
$.post('/api/web/tracks/delete/' + track.id, {_token: window.pfm.token})
|
||||
.then -> $scope.$apply ->
|
||||
$scope.$emit 'track-deleted'
|
||||
$state.transitionTo 'account-content.tracks'
|
||||
$state.transitionTo 'account.tracks'
|
||||
|
||||
$scope.$on '$locationChangeStart', (e) ->
|
||||
return if !$scope.isDirty
|
|
@ -26,67 +26,8 @@ angular.module('ponyfm').controller "account-tracks", [
|
|||
|
||||
tracks.refresh().done setTracks
|
||||
|
||||
$scope.filters =
|
||||
published: [
|
||||
{title: 'Either', query: ''},
|
||||
{title: 'Yes', query: 'published=1'},
|
||||
{title: 'No', query: 'published=0'}]
|
||||
|
||||
sort: [
|
||||
{title: 'Newest to Oldest', query: 'order=created_at,desc'},
|
||||
{title: 'Oldest to Newest', query: 'order=created_at,asc'}]
|
||||
|
||||
genres: {}
|
||||
trackTypes: {}
|
||||
|
||||
$scope.filter =
|
||||
published: $scope.filters.published[0]
|
||||
sort: $scope.filters.sort[0]
|
||||
genres: {}
|
||||
trackTypes: {}
|
||||
|
||||
$scope.titles =
|
||||
genres: 'All'
|
||||
trackTypes: 'All'
|
||||
|
||||
for genre in taxonomies.genres
|
||||
$scope.filters.genres[genre.id] =
|
||||
id: genre.id
|
||||
title: genre.name
|
||||
query: 'genres[]=' + genre.id
|
||||
|
||||
for type in taxonomies.trackTypes
|
||||
$scope.filters.trackTypes[type.id] =
|
||||
id: type.id
|
||||
title: type.title
|
||||
query: 'types[]=' + type.id
|
||||
|
||||
$scope.updateFilter = (type, filter) ->
|
||||
$scope.filter[type] = filter
|
||||
$scope.refreshList()
|
||||
|
||||
$scope.toggleFilter = (type, id) ->
|
||||
if !$scope.filter[type][id]
|
||||
$scope.filter[type][id] = $scope.filters[type][id]
|
||||
else
|
||||
delete $scope.filter[type][id]
|
||||
|
||||
length = _.keys($scope.filter[type]).length
|
||||
if length == 1
|
||||
$scope.titles[type] = _.map($scope.filter[type], (f) -> f.title).join ', '
|
||||
else if length > 1
|
||||
$scope.titles[type] = length + ' selected'
|
||||
else
|
||||
$scope.titles[type] = 'All'
|
||||
|
||||
$scope.refreshList()
|
||||
|
||||
$scope.refreshList = () ->
|
||||
parts = [$scope.filter.sort.query, $scope.filter.published.query]
|
||||
_.each $scope.filter.genres, (g) -> parts.push g.query
|
||||
_.each $scope.filter.trackTypes, (g) -> parts.push g.query
|
||||
query = parts.join '&'
|
||||
tracks.refresh(query).done setTracks
|
||||
tracks.refresh().done setTracks
|
||||
|
||||
$scope.selectTrack = (track) ->
|
||||
$scope.data.selectedTrack = track
|
||||
|
|
12
public/scripts/app/controllers/favourites-albums.coffee
Normal file
12
public/scripts/app/controllers/favourites-albums.coffee
Normal file
|
@ -0,0 +1,12 @@
|
|||
window.pfm.preloaders['favourites-albums'] = [
|
||||
'favourites'
|
||||
(favourites) ->
|
||||
favourites.fetchAlbums(true)
|
||||
]
|
||||
|
||||
angular.module('ponyfm').controller "favourites-albums", [
|
||||
'$scope', 'favourites'
|
||||
($scope, favourites) ->
|
||||
favourites.fetchAlbums().done (res) ->
|
||||
$scope.albums = res.albums
|
||||
]
|
12
public/scripts/app/controllers/favourites-playlists.coffee
Normal file
12
public/scripts/app/controllers/favourites-playlists.coffee
Normal file
|
@ -0,0 +1,12 @@
|
|||
window.pfm.preloaders['favourites-playlists'] = [
|
||||
'favourites'
|
||||
(favourites) ->
|
||||
favourites.fetchPlaylists(true)
|
||||
]
|
||||
|
||||
angular.module('ponyfm').controller "favourites-playlists", [
|
||||
'$scope', 'favourites'
|
||||
($scope, favourites) ->
|
||||
favourites.fetchPlaylists().done (res) ->
|
||||
$scope.playlists = res.playlists
|
||||
]
|
12
public/scripts/app/controllers/favourites-tracks.coffee
Normal file
12
public/scripts/app/controllers/favourites-tracks.coffee
Normal file
|
@ -0,0 +1,12 @@
|
|||
window.pfm.preloaders['favourites-tracks'] = [
|
||||
'favourites'
|
||||
(favourites) ->
|
||||
favourites.fetchTracks(true)
|
||||
]
|
||||
|
||||
angular.module('ponyfm').controller "favourites-tracks", [
|
||||
'$scope', 'favourites'
|
||||
($scope, favourites) ->
|
||||
favourites.fetchTracks().done (res) ->
|
||||
$scope.tracks = res.tracks
|
||||
]
|
|
@ -1,32 +0,0 @@
|
|||
angular.module('ponyfm').controller "upload", [
|
||||
'$scope', 'auth', 'upload', '$state'
|
||||
($scope, auth, upload, $state) ->
|
||||
$scope.$on 'upload-queue-started', () ->
|
||||
$scope.state = 'uploading'
|
||||
$scope.uploadDialogOpen = true
|
||||
$scope.uploads = {}
|
||||
$scope.progress = 0
|
||||
$scope.uploadedFiles = 0
|
||||
$scope.totalFiles = 0
|
||||
|
||||
$scope.$on 'upload-added', (e, upload) ->
|
||||
$scope.uploads[upload.index] = upload
|
||||
$scope.totalFiles++
|
||||
|
||||
$scope.$on 'upload-queue-ended', () ->
|
||||
$scope.state = 'finished'
|
||||
$scope.uploadDialogOpen = false if _.each upload.queue, (u) -> u.error == null
|
||||
$state.transitionTo 'account-content.tracks'
|
||||
$scope.uploadDialogOpen = false if !(_.size $scope.uploads)
|
||||
|
||||
$scope.$on 'upload-finished', (e, upload) ->
|
||||
$scope.uploadedFiles++
|
||||
delete $scope.uploads[upload.index] if upload.success
|
||||
|
||||
$scope.$on 'upload-progress', () ->
|
||||
$scope.progress = upload.totalBytesUploaded / upload.totalBytes * 100
|
||||
$scope.state = 'processing' if $scope.progress >= 100
|
||||
|
||||
$scope.close = () ->
|
||||
$scope.uploadDialogOpen = false
|
||||
]
|
8
public/scripts/app/controllers/uploader.coffee
Normal file
8
public/scripts/app/controllers/uploader.coffee
Normal file
|
@ -0,0 +1,8 @@
|
|||
angular.module('ponyfm').controller "uploader", [
|
||||
'$scope', 'auth', 'upload', '$state'
|
||||
($scope, auth, upload, $state) ->
|
||||
|
||||
$scope.data = upload
|
||||
|
||||
$scope.$on 'upload-finished', (e, upload) ->
|
||||
]
|
|
@ -41,7 +41,7 @@ angular.module('ponyfm').directive 'pfmPlayer', () ->
|
|||
|
||||
moveVolumeSlider = (absoluteY) ->
|
||||
newY = absoluteY - $bar.offset().top;
|
||||
maxY = $bar.height() - ($knob.height() / 2)
|
||||
maxY = $bar.height() - ($knob.height() / 2) - 8
|
||||
|
||||
newY = 0 if newY < 0
|
||||
newY = maxY if newY > maxY
|
||||
|
@ -54,11 +54,11 @@ angular.module('ponyfm').directive 'pfmPlayer', () ->
|
|||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
$slider.click (e) -> $scope.$apply -> moveVolumeSlider(e.pageY)
|
||||
$slider.click (e) -> $scope.$apply -> moveVolumeSlider(e.pageY - 8)
|
||||
|
||||
$(document).mousemove (e) ->
|
||||
return if !isSliding
|
||||
moveVolumeSlider(e.pageY)
|
||||
moveVolumeSlider(e.pageY - 8)
|
||||
|
||||
$knob.mousedown (e) ->
|
||||
e.preventDefault()
|
||||
|
|
|
@ -43,7 +43,7 @@ angular.module('ponyfm').directive 'pfmPopup', () ->
|
|||
height = windowHeight - top;
|
||||
|
||||
return {
|
||||
left: left - parentPosition.left - 5
|
||||
left: left - parentPosition.left
|
||||
top: top - parentPosition.top,
|
||||
height: height - 15}
|
||||
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
angular.module('ponyfm').directive 'uploader', [
|
||||
'upload'
|
||||
(upload) -> (scope) ->
|
||||
$body = $ 'body'
|
||||
$notice = $("<div class='file-over-notice'><p>Drop the files anywhere to begin your upload!</p></div>").appendTo($body)
|
||||
notice = $notice[0]
|
||||
(upload) -> (scope, element) ->
|
||||
$dropzone = $(element)
|
||||
|
||||
window.addEventListener 'dragover', (e) ->
|
||||
$dropzone[0].addEventListener 'dragover', (e) ->
|
||||
e.preventDefault()
|
||||
$body.addClass 'file-over'
|
||||
$dropzone.addClass 'file-over'
|
||||
|
||||
notice.addEventListener 'dragleave', (e) ->
|
||||
$dropzone[0].addEventListener 'dragleave', (e) ->
|
||||
e.preventDefault()
|
||||
$body.removeClass 'file-over'
|
||||
$dropzone.removeClass 'file-over'
|
||||
|
||||
notice.addEventListener 'drop', (e) ->
|
||||
$dropzone[0].addEventListener 'drop', (e) ->
|
||||
e.preventDefault()
|
||||
$body.removeClass 'file-over'
|
||||
$dropzone.removeClass 'file-over'
|
||||
|
||||
files = e.target.files || e.dataTransfer.files
|
||||
scope.$apply -> upload.upload files
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
angular.module('ponyfm').factory('favourites', [
|
||||
'$rootScope', '$http'
|
||||
($rootScope, $http) ->
|
||||
tracksDef = null
|
||||
playlistsDef = null
|
||||
albumsDef = null
|
||||
|
||||
self =
|
||||
toggle: (type, id) ->
|
||||
def = new $.Deferred()
|
||||
|
@ -9,5 +13,29 @@ angular.module('ponyfm').factory('favourites', [
|
|||
|
||||
def.promise()
|
||||
|
||||
fetchTracks: (force) ->
|
||||
return tracksDef if !force && tracksDef
|
||||
tracksDef = new $.Deferred()
|
||||
$http.get('/api/web/favourites/tracks').success (res) ->
|
||||
tracksDef.resolve res
|
||||
|
||||
tracksDef
|
||||
|
||||
fetchAlbums: (force) ->
|
||||
return albumsDef if !force && albumsDef
|
||||
albumsDef = new $.Deferred()
|
||||
$http.get('/api/web/favourites/albums').success (res) ->
|
||||
albumsDef.resolve res
|
||||
|
||||
albumsDef
|
||||
|
||||
fetchPlaylists: (force) ->
|
||||
return playlistsDef if !force && playlistsDef
|
||||
playlistsDef = new $.Deferred()
|
||||
$http.get('/api/web/favourites/playlists').success (res) ->
|
||||
playlistsDef.resolve res
|
||||
|
||||
playlistsDef
|
||||
|
||||
self
|
||||
])
|
|
@ -5,8 +5,8 @@ angular.module('ponyfm').factory('images', [
|
|||
self =
|
||||
images: []
|
||||
isLoading: true
|
||||
refresh: () ->
|
||||
return def if def
|
||||
refresh: (force) ->
|
||||
return def if !force && def
|
||||
def = new $.Deferred()
|
||||
|
||||
self.images = []
|
||||
|
|
|
@ -3,12 +3,8 @@ angular.module('ponyfm').factory('upload', [
|
|||
($rootScope) ->
|
||||
self =
|
||||
queue: []
|
||||
totalBytes: 0
|
||||
totalBytesUploaded: 0
|
||||
|
||||
upload: (files) ->
|
||||
$rootScope.$broadcast 'upload-queue-started' if self.queue.length == 0
|
||||
|
||||
_.each files, (file) ->
|
||||
upload =
|
||||
name: file.name
|
||||
|
@ -21,7 +17,6 @@ angular.module('ponyfm').factory('upload', [
|
|||
error: null
|
||||
|
||||
self.queue.push upload
|
||||
self.totalBytes += file.size
|
||||
$rootScope.$broadcast 'upload-added', upload
|
||||
|
||||
xhr = new XMLHttpRequest()
|
||||
|
@ -29,7 +24,6 @@ angular.module('ponyfm').factory('upload', [
|
|||
$rootScope.$apply ->
|
||||
upload.uploadedSize = e.loaded
|
||||
upload.progress = e.loaded / upload.size * 100
|
||||
self.totalBytesUploaded = _.reduce self.queue, ((i, u) -> i + u.uploadedSize), 0
|
||||
$rootScope.$broadcast 'upload-progress', upload
|
||||
|
||||
xhr.onload = -> $rootScope.$apply ->
|
||||
|
@ -45,15 +39,9 @@ angular.module('ponyfm').factory('upload', [
|
|||
$rootScope.$broadcast 'upload-error', [upload, error]
|
||||
else
|
||||
upload.success = true
|
||||
upload.trackId = $.parseJSON(xhr.responseText).id
|
||||
|
||||
$rootScope.$broadcast 'upload-finished', upload
|
||||
|
||||
if (_.every self.queue, (u) -> !u.isUploading)
|
||||
self.queue = []
|
||||
self.totalBytes = 0
|
||||
self.totalBytesUploaded = 0
|
||||
$rootScope.$broadcast 'upload-queue-ended'
|
||||
|
||||
formData = new FormData();
|
||||
formData.append('track', file);
|
||||
|
||||
|
|
|
@ -1,2 +1,418 @@
|
|||
@import-once 'base/bootstrap/bootstrap';
|
||||
@import-once 'mixins';
|
||||
|
||||
ul.playlists {
|
||||
overflow-y: auto;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
list-style: none;
|
||||
margin: 0px 5px;
|
||||
margin-top: 10px;
|
||||
|
||||
li {
|
||||
overflow: hidden;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
height: 40px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
|
||||
img {
|
||||
border-right: 1px solid #ddd;
|
||||
padding: 0px;
|
||||
display: block;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
float: right;
|
||||
display: block;
|
||||
margin-top: 7px;
|
||||
margin-right: 7px;
|
||||
|
||||
.btn {
|
||||
.border-radius(0px);
|
||||
}
|
||||
}
|
||||
|
||||
a.main {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: #444;
|
||||
|
||||
.is-public {
|
||||
display: block;
|
||||
float: right;
|
||||
font-weight: bold;
|
||||
margin-top: 10px;
|
||||
margin-right: 10px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
.title {
|
||||
.ellipsis();
|
||||
margin-left: 50px;
|
||||
line-height: 40px;
|
||||
height: 40px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.image-selector {
|
||||
width: 500px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
|
||||
li {
|
||||
margin: 0px;
|
||||
float: left;
|
||||
width: 20%;
|
||||
cursor: pointer;
|
||||
|
||||
img {
|
||||
.transition(all 400ms);
|
||||
display: block;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
img {
|
||||
opacity: .8;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
.two-pane-view.closed {
|
||||
.account-albums-listing {
|
||||
li {
|
||||
.box-sizing(border-box);
|
||||
width: 20%;
|
||||
|
||||
a {
|
||||
padding: 15px;
|
||||
|
||||
.image {
|
||||
.img-polaroid();
|
||||
position: relative;
|
||||
left: -5px;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
float: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0px;
|
||||
font-size: 12pt;
|
||||
padding: 0px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.published {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.account-tracks-listing, .account-albums-listing {
|
||||
overflow-y: auto;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
.box-sizing(border-box);
|
||||
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
line-height: normal;
|
||||
|
||||
&.empty {
|
||||
.alert();
|
||||
float: none !important;
|
||||
width: auto !important;
|
||||
display: block;
|
||||
padding: 5px;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
&.is-not-published a {
|
||||
background: fadeout(@yellow, 90%);
|
||||
}
|
||||
|
||||
&.selected, &.selected:hover {
|
||||
a {
|
||||
background: @pfm-purple;
|
||||
cursor: default;
|
||||
color: #fff;
|
||||
|
||||
.published {
|
||||
color: #eee;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
.transition(350px ease-out all);
|
||||
|
||||
display: block;
|
||||
margin: 0px;
|
||||
padding: 5px;
|
||||
font-size: 9pt;
|
||||
font-weight: normal;
|
||||
overflow: hidden;
|
||||
|
||||
.image {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
float: left;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.title {
|
||||
.ellipsis();
|
||||
display: block;
|
||||
margin-left: 37px;
|
||||
}
|
||||
|
||||
.published {
|
||||
display: block;
|
||||
color: #777;
|
||||
margin-left: 37px;
|
||||
font-size: 8pt;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #ddd;
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.two-pane-view {
|
||||
.list {
|
||||
.dropdowns {
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.editor {
|
||||
display: none;
|
||||
|
||||
.album-track-listing {
|
||||
padding: 0px;
|
||||
clear: both;
|
||||
margin: 0px;
|
||||
margin-top: 10px;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
overflow: hidden;
|
||||
line-height: normal;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 8pt;
|
||||
border-bottom: 1px dashed #ddd;
|
||||
|
||||
div {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
float: left;
|
||||
margin-left: 5px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
line-height: normal;
|
||||
padding: 2px 5px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
&.ui-sortable-helper {
|
||||
#gradient>.vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%));
|
||||
border: none;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
&.ui-sortable-placeholder {
|
||||
background: @yellow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.show-songs, .album {
|
||||
.btn {
|
||||
display: block;
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
.show-songs, .album, .track-selector {
|
||||
.btn {
|
||||
.border-radius(0px);
|
||||
padding: 3px 10px;
|
||||
font-size: 8pt;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.error {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.pfm-popup {
|
||||
width: 300px;
|
||||
|
||||
ul {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
|
||||
a {
|
||||
.ellipsis();
|
||||
display: block;
|
||||
padding: 3px 10px;
|
||||
font-size: 8pt;
|
||||
color: #333333;
|
||||
|
||||
&:hover {
|
||||
#gradient>.vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%));
|
||||
text-decoration: none;
|
||||
color: @dropdownLinkColorHover;
|
||||
}
|
||||
}
|
||||
|
||||
&.selected {
|
||||
a {
|
||||
#gradient>.vertical(@green, darken(@green, 5%));
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
#gradient>.vertical(fadeout(@green, 20%), fadeout(darken(@green, 5%), 20%));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.closed {
|
||||
.account-tracks-listing, .account-albums-listing {
|
||||
.clearfix();
|
||||
|
||||
li {
|
||||
float: left;
|
||||
width: 25%;
|
||||
|
||||
&.empty {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
.list {
|
||||
border-right: 2px solid #ddd;
|
||||
|
||||
width: 250px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.editor {
|
||||
margin-left: 260px;
|
||||
margin-right: 10px;
|
||||
display: block;
|
||||
|
||||
.stretch-to-bottom {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.license-grid {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
overflow: hidden;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
width: 25%;
|
||||
|
||||
> div {
|
||||
margin: 0px 5px;
|
||||
border: 1px solid #ddd;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
strong {
|
||||
font-size: 9pt;
|
||||
display: block;
|
||||
margin: 0px;
|
||||
margin-bottom: 5px;
|
||||
padding: 0px;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
min-height: 120px;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
a {
|
||||
.border-radius(0px);
|
||||
display: block;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
> div {
|
||||
cursor: default;
|
||||
border-color: @blue;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover > div {
|
||||
border: 1px solid #3366CC;
|
||||
}
|
||||
|
||||
&:first-child > div {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
&:last-child > div {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,4 +12,5 @@
|
|||
@import 'body';
|
||||
@import 'player';
|
||||
@import 'content';
|
||||
@import 'dashboard';
|
||||
@import 'dashboard';
|
||||
@import 'uploader';
|
|
@ -33,6 +33,50 @@ header {
|
|||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.user-details {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
margin-top: 9px;
|
||||
|
||||
.avatar {
|
||||
.img-polaroid();
|
||||
|
||||
display: block;
|
||||
float: right;
|
||||
padding: 2px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
span {
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
padding: 0px 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
left: auto;
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.name {
|
||||
float: left;
|
||||
height: 44px;
|
||||
line-height: 44px;
|
||||
padding-right: 10px;
|
||||
font-weight: bold;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.now-playing {
|
||||
|
@ -52,6 +96,7 @@ header {
|
|||
padding: 0px;
|
||||
margin: 0px;
|
||||
font-size: 10pt;
|
||||
position: relative;
|
||||
|
||||
li {
|
||||
margin: 0px;
|
||||
|
@ -128,6 +173,11 @@ header {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
li.uploader {
|
||||
a {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.site-content {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@import-once 'mixins';
|
||||
|
||||
.track-player {
|
||||
overflow: hidden;
|
||||
margin-right: 75px;
|
||||
padding: 10px;
|
||||
|
||||
.image {
|
||||
|
@ -106,6 +106,47 @@
|
|||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&.volume {
|
||||
position: relative;
|
||||
|
||||
.volume-slider {
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 33px;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
background: #ddd;
|
||||
|
||||
.bar {
|
||||
background: @pfm-purple;
|
||||
width: 10px;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.knob {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: -3px;
|
||||
|
||||
background: darken(@pfm-purple, 20%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &.keep-open {
|
||||
.volume-slider {
|
||||
display: block;
|
||||
}
|
||||
|
||||
background: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ html {
|
|||
border-left: 3px solid #111;
|
||||
|
||||
.buttons {
|
||||
padding: 5px;
|
||||
background: #222;
|
||||
|
||||
.clear-button {
|
||||
|
@ -18,6 +19,10 @@ html {
|
|||
}
|
||||
|
||||
.open-button {
|
||||
font-size: inherit;
|
||||
padding: 2px 10px;
|
||||
text-indent: 0px;
|
||||
|
||||
i:before {
|
||||
content: "\f077";
|
||||
}
|
||||
|
@ -42,7 +47,6 @@ html {
|
|||
color: #fff;
|
||||
|
||||
.buttons {
|
||||
padding: 5px;
|
||||
overflow: hidden;
|
||||
|
||||
> a {
|
||||
|
@ -60,6 +64,13 @@ html {
|
|||
margin-right: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.open-button {
|
||||
font-size: 1pt;
|
||||
padding: 2px;
|
||||
text-indent: -1000px;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
ul {
|
||||
|
|
82
public/styles/uploader.less
Normal file
82
public/styles/uploader.less
Normal file
|
@ -0,0 +1,82 @@
|
|||
@import-once 'variables';
|
||||
|
||||
.uploader {
|
||||
h1 {
|
||||
margin: 10px 0px;
|
||||
}
|
||||
|
||||
.dropzone {
|
||||
border: 2px dotted @pfm-purple;
|
||||
background: lighten(@pfm-purple, 25%);
|
||||
padding: 10px;
|
||||
color: darken(@pfm-purple, 25%);
|
||||
margin-bottom: 10px;
|
||||
|
||||
p {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
&.file-over {
|
||||
background: lighten(@pfm-purple, 5%);
|
||||
}
|
||||
}
|
||||
|
||||
.close-button {
|
||||
.border-radius(0px);
|
||||
width: auto;
|
||||
margin-bottom: 10px;
|
||||
float: none;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.uploads {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
list-style: none;
|
||||
|
||||
li {
|
||||
margin-bottom: 5px;
|
||||
background: #eee;
|
||||
font-size: 9pt;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
color: #444;
|
||||
border: 2px solid #aaa;
|
||||
|
||||
p {
|
||||
margin: 5px;
|
||||
padding: 0px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
|
||||
a {
|
||||
}
|
||||
}
|
||||
|
||||
.bar {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&.has-error {
|
||||
border-color: @red;
|
||||
|
||||
.bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-processing {
|
||||
border-color: @blue;
|
||||
|
||||
.bar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
public/templates/account/_layout.html
Normal file
8
public/templates/account/_layout.html
Normal file
|
@ -0,0 +1,8 @@
|
|||
<ul class="tabs">
|
||||
<li ng-class="{active: stateIncludes('account.tracks')}"><a href="/account/tracks">Tracks</a></li>
|
||||
<li ng-class="{active: stateIncludes('account.albums')}"><a href="/account/albums">Albums</a></li>
|
||||
<li ng-class="{active: stateIncludes('account.playlists')}"><a href="/account/playlists">Playlists</a></li>
|
||||
<li ng-class="{active: stateIncludes('account.settings')}"><a href="/account">Settings</a></li>
|
||||
</ul>
|
||||
|
||||
<ui-view></ui-view>
|
|
@ -1,8 +0,0 @@
|
|||
<div>
|
||||
<ul class="tabs">
|
||||
<li ng-class="{active: stateIncludes('account-content.tracks')}"><a href="/account/tracks">Tracks</a></li>
|
||||
<li ng-class="{active: stateIncludes('account-content.albums')}"><a href="/account/albums">Albums</a></li>
|
||||
</ul>
|
||||
|
||||
<ui-view></ui-view>
|
||||
</div>
|
|
@ -1,25 +0,0 @@
|
|||
<div>
|
||||
<h1>Your Playlists</h1>
|
||||
<ul class="playlists stretch-to-bottom">
|
||||
<li ng-repeat="playlist in playlists">
|
||||
<div class="btn-group">
|
||||
<a href="#" pfm-eat-click class="btn btn-small" ng-click="togglePlaylistPin(playlist)" ng-class="{active: playlist.is_pinned, 'btn-primary': playlist.is_pinned}">
|
||||
<i class="icon-pushpin"></i>
|
||||
</a>
|
||||
<a href="#" pfm-eat-click class="btn btn-small" ng-click="editPlaylist(playlist)">Edit</a>
|
||||
<a href="#" pfm-eat-click class="btn btn-small btn-danger" ng-click="deletePlaylist(playlist)">Delete</a>
|
||||
</div>
|
||||
<a href="{{playlist.url}}" class="main">
|
||||
<span class="is-public">
|
||||
<span ng-show="playlist.is_public">Is Public</span>
|
||||
<span ng-hide="playlist.is_public">Is Private</span>
|
||||
</span>
|
||||
|
||||
<img ng-src="{{playlist.covers.small}}" />
|
||||
<span class="title">
|
||||
{{playlist.title}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
|
@ -1,60 +0,0 @@
|
|||
<div>
|
||||
<ul class="dropdowns">
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Published: <strong>{{filter.published.title}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="filter in filters.published">
|
||||
<a pfm-eat-click href="#" ng-click="updateFilter('published', filter)">{{filter.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Sort: <strong>{{filter.sort.title}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="filter in filters.sort">
|
||||
<a pfm-eat-click href="#" ng-click="updateFilter('sort', filter)">{{filter.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Type: <strong>{{titles.trackTypes}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<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>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a class="dropdown-toggle btn">
|
||||
Genera: <strong>{{titles.genres}}</strong>
|
||||
</a>
|
||||
<ul class="dropdown-menu">
|
||||
<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.title}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="two-pane-view" ng-class="{open: data.selectedTrack != null, closed: data.selectedTrack == null}">
|
||||
<div class="list">
|
||||
<ul class="account-tracks-listing stretch-to-bottom">
|
||||
<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}}" ng-click="selectTrack(track)">
|
||||
<img class="image" ng-src="{{track.cover_url}}" />
|
||||
<span class="title">{{track.title}}</span>
|
||||
<span class="published">{{track.created_at | pfmdate:'MM/dd/yyyy'}}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ui-view class="editor"></ui-view>
|
||||
</div>
|
||||
</div>
|
|
@ -1,14 +0,0 @@
|
|||
<div>
|
||||
<ul class="tabs">
|
||||
<li ng-class="{active: stateIncludes('account-favourites.tracks')}"><a href="/account/favourites">Tracks</a></li>
|
||||
<li ng-class="{active: stateIncludes('account-favourites.albums')}"><a href="/account/favourites/albums">Albums</a></li>
|
||||
<li ng-class="{active: stateIncludes('account-favourites.playlists')}"><a href="/account/favourites/playlists">Playlists</a></li>
|
||||
</ul>
|
||||
|
||||
<ui-view></ui-view>
|
||||
|
||||
<pre>
|
||||
$state = {{$state.current.name}}
|
||||
$stateParams = {{$stateParams}}
|
||||
</pre>
|
||||
</div>
|
|
@ -1 +0,0 @@
|
|||
<h2>Favourite Albums</h2>
|
|
@ -1 +0,0 @@
|
|||
<h2>Favourite Playlists</h2>
|
|
@ -1 +0,0 @@
|
|||
<h2>Favourite tracks</h2>
|
22
public/templates/account/playlists.html
Normal file
22
public/templates/account/playlists.html
Normal file
|
@ -0,0 +1,22 @@
|
|||
<ul class="playlists stretch-to-bottom">
|
||||
<li ng-repeat="playlist in playlists">
|
||||
<div class="btn-group">
|
||||
<a href="#" pfm-eat-click class="btn btn-small" ng-click="togglePlaylistPin(playlist)" ng-class="{active: playlist.is_pinned, 'btn-primary': playlist.is_pinned}">
|
||||
<i class="icon-pushpin"></i>
|
||||
</a>
|
||||
<a href="#" pfm-eat-click class="btn btn-small" ng-click="editPlaylist(playlist)">Edit</a>
|
||||
<a href="#" pfm-eat-click class="btn btn-small btn-danger" ng-click="deletePlaylist(playlist)">Delete</a>
|
||||
</div>
|
||||
<a href="{{playlist.url}}" class="main">
|
||||
<span class="is-public">
|
||||
<span ng-show="playlist.is_public">Is Public</span>
|
||||
<span ng-hide="playlist.is_public">Is Private</span>
|
||||
</span>
|
||||
|
||||
<img ng-src="{{playlist.covers.small}}" />
|
||||
<span class="title">
|
||||
{{playlist.title}}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
|
@ -1,44 +1,36 @@
|
|||
<div>
|
||||
<h1>Account Settings</h1>
|
||||
<form ng-submit="updateAccount()" class="pfm-form account-settings-form">
|
||||
<ul class="toolbar">
|
||||
<li>
|
||||
<button type="submit" class="btn" ng-class="{disabled: !isDirty || isSaving, 'btn-primary': isDirty}">
|
||||
Save Changes
|
||||
<i ng-show="isSaving" class="icon-cog icon-spin icon-large"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="stretch-to-bottom">
|
||||
<div class="row-fluid">
|
||||
<div class="form-row span6" ng-class="{'has-error': errors.display_name != null}">
|
||||
<label for="sync_names" class="strong"><input ng-disabled="isSaving" ng-change="touchModel();" id="sync_names" type="checkbox" ng-model="settings.sync_names" /> Sync my MLP Forums display name with Pony.fm</label>
|
||||
<input type="text" ng-disabled="isSaving" ng-change="touchModel()" ng-show="!settings.sync_names" placeholder="Display Name" id="display_name" ng-model="settings.display_name" />
|
||||
<div ng-show="settings.sync_names" class="alert alert-info">Your current MLP Forums display name is <strong>{{settings.mlpforums_name}}</strong></div>
|
||||
<div class="error">{{errors.display_name}}</div>
|
||||
</div>
|
||||
<div class="form-row span6">
|
||||
<label for="can_see_explicit_content"><input ng-change="touchModel()" ng-disabled="isLoading" id="can_see_explicit_content" type="checkbox" ng-model="settings.can_see_explicit_content" /> Can See Explicit Content</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-fluid">
|
||||
<div class="form-row span6" ng-class="{'has-error': errors.bio != null}">
|
||||
<label class="strong" for="bio">Bio</label>
|
||||
<textarea id="bio" placeholder="bio (optional)" ng-model="settings.bio" ng-disabled="isLoading" ng-change="touchModel()"></textarea>
|
||||
<div class="error">{{errors.description}}</div>
|
||||
</div>
|
||||
<div class="form-row span6" ng-class="{'has-error': errors.avatar != null || errors.gravatar != null}">
|
||||
<label for="uses_gravatar">
|
||||
<input ng-change="touchModel()" ng-disabled="isLoading" id="uses_gravatar" type="checkbox" ng-model="settings.uses_gravatar" /> Use Gravatar
|
||||
</label>
|
||||
<div ng-show="!settings.uses_gravatar">
|
||||
<pfm-image-upload set-image="setAvatar" image="settings.avatar_url" />
|
||||
</div>
|
||||
<input type="text" ng-disabled="isSaving" ng-change="touchModel()" ng-show="settings.uses_gravatar" placeholder="Gravatar Email" ng-model="settings.gravatar" />
|
||||
<div class="error" ng-show="errors.avatar != null">{{errors.avatar}}</div>
|
||||
<div class="error" ng-show="errors.gravatar != null">{{errors.gravatar}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<form ng-submit="updateAccount()" class="pfm-form account-settings-form">
|
||||
<ul class="toolbar">
|
||||
<li>
|
||||
<button type="submit" class="btn" ng-class="{disabled: !isDirty || isSaving, 'btn-primary': isDirty}">
|
||||
Save Changes
|
||||
<i ng-show="isSaving" class="icon-cog icon-spin icon-large"></i>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="stretch-to-bottom">
|
||||
<div class="form-row" ng-class="{'has-error': errors.display_name != null}">
|
||||
<label for="sync_names" class="strong"><input ng-disabled="isSaving" ng-change="touchModel();" id="sync_names" type="checkbox" ng-model="settings.sync_names" /> Sync my MLP Forums display name with Pony.fm</label>
|
||||
<input type="text" ng-disabled="isSaving" ng-change="touchModel()" ng-show="!settings.sync_names" placeholder="Display Name" id="display_name" ng-model="settings.display_name" />
|
||||
<div ng-show="settings.sync_names" class="alert alert-info">Your current MLP Forums display name is <strong>{{settings.mlpforums_name}}</strong></div>
|
||||
<div class="error">{{errors.display_name}}</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<label for="can_see_explicit_content" class="strong"><input ng-change="touchModel()" ng-disabled="isLoading" id="can_see_explicit_content" type="checkbox" ng-model="settings.can_see_explicit_content" /> Can See Explicit Content</label>
|
||||
</div>
|
||||
<div class="form-row" ng-class="{'has-error': errors.bio != null}">
|
||||
<label class="strong" for="bio">Bio</label>
|
||||
<textarea id="bio" placeholder="bio (optional)" ng-model="settings.bio" ng-disabled="isLoading" ng-change="touchModel()"></textarea>
|
||||
<div class="error">{{errors.description}}</div>
|
||||
</div>
|
||||
<div class="form-row" ng-class="{'has-error': errors.avatar != null || errors.gravatar != null}">
|
||||
<label for="uses_gravatar" class="strong">
|
||||
<input ng-change="touchModel()" ng-disabled="isLoading" id="uses_gravatar" type="checkbox" ng-model="settings.uses_gravatar" /> Use Gravatar
|
||||
</label>
|
||||
<div ng-show="!settings.uses_gravatar">
|
||||
<pfm-image-upload set-image="setAvatar" image="settings.avatar_url" />
|
||||
</div>
|
||||
<input type="text" ng-disabled="isSaving" ng-change="touchModel()" ng-show="settings.uses_gravatar" placeholder="Gravatar Email" ng-model="settings.gravatar" />
|
||||
<div class="error" ng-show="errors.avatar != null">{{errors.avatar}}</div>
|
||||
<div class="error" ng-show="errors.gravatar != null">{{errors.gravatar}}</div>
|
||||
</div>
|
||||
</form>
|
||||
|
|
15
public/templates/account/tracks.html
Normal file
15
public/templates/account/tracks.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<div class="two-pane-view" ng-class="{open: data.selectedTrack != null, closed: data.selectedTrack == null}">
|
||||
<div class="list">
|
||||
<ul class="account-tracks-listing stretch-to-bottom">
|
||||
<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}}" ng-click="selectTrack(track)">
|
||||
<img class="image" ng-src="{{track.cover_url}}" />
|
||||
<span class="title">{{track.title}}</span>
|
||||
<span class="published">{{track.created_at | pfmdate:'MM/dd/yyyy'}}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<ui-view class="editor"></ui-view>
|
||||
</div>
|
|
@ -1,3 +1,3 @@
|
|||
<div class="stretch-to-bottom">
|
||||
<pfm-albums-list albums="albums" />
|
||||
<pfm-albums-list albums="albums"></pfm-albums-list>
|
||||
</div>
|
|
@ -10,6 +10,7 @@
|
|||
</li>
|
||||
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share or Embed</a></li>
|
||||
<li><pfm-favourite-button resource="album" type="album"></pfm-favourite-button></li>
|
||||
<li bo-show="album.permissions.edit"><a class="btn btn-small" bo-href="'/account/albums/edit/' + album.id">Edit</a></li>
|
||||
</ul>
|
||||
|
||||
<header>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
</a>
|
||||
</li>
|
||||
<li><a pfm-eat-click ng-click="playNext()" class="next" href="#"><i class="icon-fast-forward"></i></a></li>
|
||||
<li>
|
||||
<li class="volume">
|
||||
<a pfm-eat-click ng-click="" class="volume" href="#">
|
||||
<i class="icon-volume-up"></i>
|
||||
</a>
|
||||
|
|
14
public/templates/favourites/_layout.html
Normal file
14
public/templates/favourites/_layout.html
Normal file
|
@ -0,0 +1,14 @@
|
|||
<div>
|
||||
<ul class="tabs">
|
||||
<li ng-class="{active: stateIncludes('favourites.tracks')}"><a href="/account/favourites/tracks">Tracks</a></li>
|
||||
<li ng-class="{active: stateIncludes('favourites.albums')}"><a href="/account/favourites/albums">Albums</a></li>
|
||||
<li ng-class="{active: stateIncludes('favourites.playlists')}"><a href="/account/favourites/playlists">Playlists</a></li>
|
||||
</ul>
|
||||
|
||||
<ui-view></ui-view>
|
||||
|
||||
<pre>
|
||||
$state = {{$state.current.name}}
|
||||
$stateParams = {{$stateParams}}
|
||||
</pre>
|
||||
</div>
|
3
public/templates/favourites/albums.html
Normal file
3
public/templates/favourites/albums.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="stretch-to-bottom">
|
||||
<pfm-albums-list albums="albums" />
|
||||
</div>
|
3
public/templates/favourites/playlists.html
Normal file
3
public/templates/favourites/playlists.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="stretch-to-bottom">
|
||||
<pfm-playlists-list playlists="playlists"></pfm-playlists-list>
|
||||
</div>
|
|
@ -1,19 +0,0 @@
|
|||
<div class="overlay upload-dialog" ng-controller="upload" ng-show="uploadDialogOpen">
|
||||
<div class="inner">
|
||||
<h2 ng-show="state == 'uploading'">
|
||||
<span>{{uploadedFiles}} of {{totalFiles}}</span>
|
||||
Uploading Tracks...
|
||||
</h2>
|
||||
<h2 ng-show="state == 'processing'">Processing...</h2>
|
||||
|
||||
<a href="#" pfm-eat-click class="btn btn-primary close-button" ng-click="close()" ng-show="state == 'finished'">Close <i class="icon-remove"></i></a>
|
||||
|
||||
<ul class="uploads">
|
||||
<li ng-repeat="upload in uploads" ng-class="{'has-error': upload.error != null, 'is-processing': upload.isUploading && upload.error == null && upload.progress >= 100}" ng-animate="'upload-queue'">
|
||||
<p><strong>{{upload.name}}</strong></p>
|
||||
<p ng-show="upload.error != null">{{upload.error}}</p>
|
||||
<div class="bar" pfm-progress-bar="upload.progress"></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
|
@ -1,3 +1,3 @@
|
|||
<div class="stretch-to-bottom">
|
||||
<pfm-playlists-list playlists="playlists" />
|
||||
<pfm-playlists-list playlists="playlists"></pfm-playlists-list>
|
||||
</div>
|
3
public/templates/tracks/list.html
Normal file
3
public/templates/tracks/list.html
Normal file
|
@ -0,0 +1,3 @@
|
|||
<div class="stretch-to-bottom">
|
||||
<pfm-tracks-list tracks="tracks" class="three-columns"></pfm-tracks-list>
|
||||
</div>
|
|
@ -24,6 +24,7 @@
|
|||
</li>
|
||||
<li><a href="#" class="btn" pfm-eat-click ng-click="share()">Share</a></li>
|
||||
<li><pfm-favourite-button resource="track" type="track"></pfm-favourite-button></li>
|
||||
<li bo-show="track.permissions.edit"><a class="btn btn-small" bo-href="'/account/tracks/edit/' + track.id">Edit</a></li>
|
||||
</ul>
|
||||
|
||||
<header>
|
||||
|
|
25
public/templates/uploader/index.html
Normal file
25
public/templates/uploader/index.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
<div class="uploader">
|
||||
<div class="dropzone" uploader>
|
||||
<p>Drop files here to begin your upload!</p>
|
||||
</div>
|
||||
|
||||
<ul class="uploads">
|
||||
<li ng-repeat="upload in data.queue" ng-class="{'has-error': upload.error != null, 'is-processing': upload.isUploading && upload.error == null && upload.progress >= 100}" ng-animate="'upload-queue'">
|
||||
<p>
|
||||
<span ng-show="!upload.success">
|
||||
<strong ng-show="upload.isUploading && upload.error == null && upload.progress >= 100">Processing</strong>
|
||||
<strong ng-hide="upload.isUploading && upload.error == null && upload.progress >= 100">Uploading</strong>
|
||||
{{upload.name}}
|
||||
</span>
|
||||
<span ng-show="upload.success">
|
||||
<a href="/account/tracks/edit/{{upload.trackId}}" class="btn btn-small btn-primary">
|
||||
Publish
|
||||
</a>
|
||||
{{upload.name}}
|
||||
</span>
|
||||
</p>
|
||||
<p ng-show="upload.error != null">{{upload.error}}</p>
|
||||
<div class="bar" pfm-progress-bar="upload.progress"></div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
Loading…
Reference in a new issue