Continued playlist handling

This commit is contained in:
emeric
2018-02-20 14:02:28 +01:00
parent 6060296649
commit f40df1fc29
16 changed files with 190 additions and 94 deletions
+13 -4
View File
@@ -22,6 +22,14 @@
${entries}
</message>
<message id="btn-playlist-entry-play-btn">
<i class="fa fa-play"></i>
</message>
<message id="btn-playlist-entry-del-btn">
<i class="fa fa-remove"></i>
</message>
<message id="template-playlist-entry">
<div class="media">
<div class="pull-left media-middle">
@@ -30,11 +38,12 @@
<div class="media-body">
<h4 class="media-heading">${name}</h4>
</div>
</div>
${<if-has-release>}${release-name}${</if-has-release>}
${<if-has-artist>}${artist-name}${</if-has-artist>}
${play-btn} ${remove-btn}
${<if-has-release>}${release-name}${</if-has-release>}
${<if-has-artist>}${artist-name}${</if-has-artist>}
${play-btn} ${del-btn}
</div>
</message>
</messages>
+19 -9
View File
@@ -61,6 +61,12 @@ Playlist::getAll(Wt::Dbo::Session& session, Wt::Dbo::ptr<User> user)
return std::vector<Playlist::pointer>(res.begin(), res.end());
}
void
Playlist::addTrack(Wt::Dbo::ptr<Track> track)
{
_entries.insert( PlaylistEntry::create(*session(), track, self(), _entries.size()) );
}
PlaylistEntry::PlaylistEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist, int pos)
: _pos(pos),
_track(track),
@@ -74,19 +80,23 @@ PlaylistEntry::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::
{
return session.add( new PlaylistEntry( track, playlist, pos) );
}
/*
std::vector<Track::id_type>
PlaylistEntry::getEntries(Wt::Dbo::Session& session, Playlist::pointer playlist)
std::vector<Wt::Dbo::ptr<Track>>
Playlist::getTracks(int offset, int size) const
{
typedef Wt::Dbo::collection<pointer> Entries;
assert(session());
Entries entries = session.find<PlaylistEntry>().where("playlist_id = ?").bind(playlist.id()).orderBy("pos");
Wt::Dbo::collection<Wt::Dbo::ptr<PlaylistEntry>> entries = session()->find<PlaylistEntry>().where("playlist_id = ?").bind(self().id()).orderBy("pos").offset(offset).limit(size);
std::vector<Track::id_type> res;
for (Entries::iterator it = entries.begin(); it != entries.end(); ++it)
res.push_back((*it)->getTrack().id());
std::vector<Wt::Dbo::ptr<Track>> res;
for (auto entry : entries)
{
res.push_back(entry->getTrack());
}
return res;
}*/
}
} // namespace Database
+5 -6
View File
@@ -30,7 +30,7 @@ class PlaylistEntry;
class User;
class Track;
class Playlist
class Playlist : public Wt::Dbo::Dbo<Playlist>
{
public:
typedef Wt::Dbo::ptr<Playlist> pointer;
@@ -50,12 +50,11 @@ class Playlist
bool isPublic() const { return _isPublic; }
// Modifiers
void addTrack(id_type trackId);
void addTracks(const std::vector<id_type>& trackIds);
void clear();
void addTrack(Wt::Dbo::ptr<Track> track);
void clear() { _entries.clear(); }
// Get tracks, ordered by position
std::vector<Track::id_type> getEntries(Wt::Dbo::Session& session, int offset = -1, int size = -1);
std::vector<Wt::Dbo::ptr<Track>> getTracks(int offset = -1, int size = -1) const;
template<class Action>
void persist(Action& a)
@@ -82,7 +81,7 @@ class PlaylistEntry
typedef Wt::Dbo::ptr<PlaylistEntry> pointer;
PlaylistEntry();
PlaylistEntry(Wt::Dbo::ptr<Track> rack, Wt::Dbo::ptr<Playlist> playlist, int position);
PlaylistEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist, int position);
// Create utility
static pointer create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist, int position);
+1 -1
View File
@@ -84,7 +84,7 @@ User::getAll(Wt::Dbo::Session& session)
}
User::pointer
User::getById(Wt::Dbo::Session& session, std::string id)
User::getById(Wt::Dbo::Session& session, id_type id)
{
return session.find<User>().where("id = ?").bind( id );
}
+1 -1
View File
@@ -65,7 +65,7 @@ class User
typedef Wt::Dbo::ptr<User> pointer;
// accessors
static pointer getById(Wt::Dbo::Session& session, std::string id);
static pointer getById(Wt::Dbo::Session& session, id_type id);
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
static std::string getId(pointer user);
+8 -12
View File
@@ -42,28 +42,24 @@ Artists::Artists(Filters* filters, Wt::WContainerWidget* parent)
auto artists = new Wt::WTemplate(Wt::WString::tr("template-artists"), this);
artists->addFunction("tr", &Wt::WTemplate::Functions::tr);
auto search = new Wt::WLineEdit();
artists->bindWidget("search", search);
search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder"));
search->textInput().connect(std::bind([this, search]
{
auto keywords = splitString(search->text().toUTF8(), " ");
refresh(keywords);
}));
_search = new Wt::WLineEdit();
artists->bindWidget("search", _search);
_search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder"));
_search->textInput().connect(this, &Artists::refresh);
_artistsContainer = new Wt::WContainerWidget();
artists->bindWidget("artists", _artistsContainer);
refresh();
filters->updated().connect(std::bind([=] {
refresh();
}));
filters->updated().connect(this, &Artists::refresh);
}
void
Artists::refresh(std::vector<std::string> searchKeywords)
Artists::refresh()
{
auto searchKeywords = splitString(_search->text().toUTF8(), " ");
_artistsContainer->clear();
auto clusterIds = _filters->getClusterIds();
+3 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
#include <Wt/WSignal>
namespace UserInterface {
@@ -35,9 +36,10 @@ class Artists : public Wt::WContainerWidget
Wt::Signal<Database::id_type> artistPlay;
private:
void refresh(std::vector<std::string> searchKeywords = std::vector<std::string>());
void refresh();
Filters* _filters;
Wt::WLineEdit* _search;
Wt::WContainerWidget* _artistsContainer;
};
+29 -20
View File
@@ -126,11 +126,9 @@ Explore::Explore(Wt::WContainerWidget* parent)
}
// TODO SQL this?
static std::vector<Database::id_type> getArtistTracks(Wt::Dbo::Session& session, Database::id_type artistId, std::set<Database::id_type> clusters)
static std::vector<Database::Track::pointer> getArtistTracks(Wt::Dbo::Session& session, Database::id_type artistId, std::set<Database::id_type> clusters)
{
std::vector<Database::id_type> res;
Wt::Dbo::Transaction transaction(session);
std::vector<Database::Track::pointer> res;
auto artist = Database::Artist::getById(session, artistId);
if (!artist)
@@ -140,31 +138,30 @@ static std::vector<Database::id_type> getArtistTracks(Wt::Dbo::Session& session,
for (auto release : releases)
{
auto tracks = release->getTracks(clusters);
for (auto track : tracks)
{
res.push_back(track.id());
}
res.insert( res.end(), tracks.begin(), tracks.end() );
}
return res;
}
static std::vector<Database::id_type> getReleaseTracks(Wt::Dbo::Session& session, Database::id_type releaseId, std::set<Database::id_type> clusters)
static std::vector<Database::Track::pointer> getReleaseTracks(Wt::Dbo::Session& session, Database::id_type releaseId, std::set<Database::id_type> clusters)
{
std::vector<Database::id_type> res;
Wt::Dbo::Transaction transaction(session);
std::vector<Database::Track::pointer> res;
auto release = Database::Release::getById(session, releaseId);
if (!release)
return res;
auto tracks = release->getTracks(clusters);
for (auto track : tracks)
{
res.push_back(track.id());
}
return release->getTracks(clusters);
}
static std::vector<Database::Track::pointer> getTrack(Wt::Dbo::Session& session, Database::id_type trackId)
{
std::vector<Database::Track::pointer> res;
auto track = Database::Track::getById(session, trackId);
if (track)
res.push_back(track);
return res;
}
@@ -172,37 +169,49 @@ static std::vector<Database::id_type> getReleaseTracks(Wt::Dbo::Session& session
void
Explore::handleArtistAdd(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
tracksAdd.emit(getArtistTracks(DboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleArtistPlay(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
tracksPlay.emit(getArtistTracks(DboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleReleaseAdd(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
tracksAdd.emit(getReleaseTracks(DboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleReleasePlay(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
tracksPlay.emit(getReleaseTracks(DboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleTrackAdd(Database::id_type id)
{
tracksAdd.emit(std::vector<Database::id_type>(1, id));
Wt::Dbo::Transaction transaction(DboSession());
tracksAdd.emit(getTrack(DboSession(), id));
}
void
Explore::handleTrackPlay(Database::id_type id)
{
tracksPlay.emit(std::vector<Database::id_type>(1, id));
Wt::Dbo::Transaction transaction(DboSession());
tracksPlay.emit(getTrack(DboSession(), id));
}
} // namespace UserInterface
+2 -2
View File
@@ -32,8 +32,8 @@ class Explore : public Wt::WContainerWidget
public:
Explore(Wt::WContainerWidget *parent = 0);
Wt::Signal<std::vector<Database::id_type>> tracksAdd;
Wt::Signal<std::vector<Database::id_type>> tracksPlay;
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd;
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay;
private:
+4 -3
View File
@@ -123,7 +123,8 @@ const Wt::Auth::User& CurrentAuthUser()
Database::User::pointer CurrentUser()
{
return DbHandler().getCurrentUser();
return Database::User::getById(DboSession(), 1);
// return DbHandler().getCurrentUser();
}
ImageResource* SessionImageResource()
@@ -276,12 +277,12 @@ LmsApplication::handleAuthEvent(void)
mainStack->addWidget(playlist);
mainStack->addWidget(new Wt::WText("SETTINGS"));
explore->tracksAdd.connect(std::bind([=] (std::vector<Database::id_type> tracks)
explore->tracksAdd.connect(std::bind([=] (std::vector<Database::Track::pointer> tracks)
{
playlist->addTracks(tracks);
}, std::placeholders::_1));
explore->tracksPlay.connect(std::bind([=] (std::vector<Database::id_type> tracks)
explore->tracksPlay.connect(std::bind([=] (std::vector<Database::Track::pointer> tracks)
{
playlist->playTracks(tracks);
}, std::placeholders::_1));
+81 -6
View File
@@ -17,13 +17,18 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WAnchor>
#include <Wt/WImage>
#include <Wt/WTemplate>
#include <Wt/WText>
#include "LmsApplication.hpp"
#include "PlaylistView.hpp"
namespace UserInterface {
static const std::string currentPlaylistName = "__current__playlist__";
Playlist::Playlist(Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent)
{
@@ -41,26 +46,96 @@ Playlist::Playlist(Wt::WContainerWidget* parent)
_entriesContainer = new Wt::WContainerWidget();
t->bindWidget("entries", _entriesContainer);
refresh();
}
void
Playlist::addTracks(const std::vector<Database::id_type>& trackIds)
Playlist::addTracks(const std::vector<Database::Track::pointer>& tracks)
{
for (auto trackId : trackIds)
std::cerr << "Adding track " << trackId << std::endl;
// Use a "session" playlist in order to store the current playlist
// so that the user can disconnect and get its playlist back
auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser());
if (!playlist)
{
playlist = Database::Playlist::create(DboSession(), currentPlaylistName, false, CurrentUser());
}
for (auto track : tracks)
{
playlist.modify()->addTrack(track);
}
refresh();
}
void
Playlist::playTracks(const std::vector<Database::id_type>& trackIds)
Playlist::playTracks(const std::vector<Database::Track::pointer>& tracks)
{
for (auto trackId : trackIds)
std::cerr << "Playing track " << trackId << std::endl;
Wt::Dbo::Transaction transaction(DboSession());
auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser());
if (playlist)
playlist.modify()->clear();
_entriesContainer->clear();
addTracks(tracks);
// TODO Immediate play
}
void
Playlist::refresh()
{
Wt::Dbo::Transaction transaction (DboSession());
auto playlist = Database::Playlist::get(DboSession(), currentPlaylistName, CurrentUser());
if (!playlist)
return;
auto tracks = playlist->getTracks(_entriesContainer->count(), 20); // TODO
for (auto track : tracks)
{
Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("template-playlist-entry"), _entriesContainer);
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
auto artist = track->getArtist();
if (artist)
{
entry->setCondition("if-has-artist", true);
Wt::WAnchor *artistAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(track->getArtist().id())));
Wt::WText *artistText = new Wt::WText(artistAnchor);
artistText->setText(Wt::WString::fromUTF8(artist->getName(), Wt::PlainText));
entry->bindWidget("artist-name", artistAnchor);
}
auto release = track->getRelease();
if (release)
{
entry->setCondition("if-has-release", true);
// TODO anchor
Wt::WAnchor *releaseAnchor = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(track->getRelease().id())));
Wt::WText *releaseText = new Wt::WText(releaseAnchor);
releaseText->setText(Wt::WString::fromUTF8(release->getName(), Wt::PlainText));
entry->bindWidget("release-name", releaseAnchor);
}
Wt::WImage *cover = new Wt::WImage();
cover->setImageLink(SessionImageResource()->getTrackUrl(track.id(), 64));
// Some images may not be square
cover->setWidth(64);
entry->bindWidget("cover", cover);
auto playBtn = new Wt::WText(Wt::WString::tr("btn-playlist-entry-play-btn"), Wt::XHTMLText);
entry->bindWidget("play-btn", playBtn);
// TODO
auto addBtn = new Wt::WText(Wt::WString::tr("btn-playlist-entry-del-btn"), Wt::XHTMLText);
entry->bindWidget("del-btn", addBtn);
// TODO
}
}
} // namespace UserInterface
+2 -2
View File
@@ -30,8 +30,8 @@ class Playlist : public Wt::WContainerWidget
public:
Playlist(Wt::WContainerWidget* parent = 0);
void addTracks(const std::vector<Database::id_type>& trackIds);
void playTracks(const std::vector<Database::id_type>& trackIds);
void addTracks(const std::vector<Database::Track::pointer>& tracks);
void playTracks(const std::vector<Database::Track::pointer>& tracks);
private:
void refresh();
+8 -13
View File
@@ -19,7 +19,6 @@
#include <Wt/WApplication>
#include <Wt/WAnchor>
#include <Wt/WLineEdit>
#include <Wt/WImage>
#include <Wt/WText>
#include <Wt/WTemplate>
@@ -44,29 +43,25 @@ Releases::Releases(Filters* filters, Wt::WContainerWidget* parent)
auto releases = new Wt::WTemplate(Wt::WString::tr("template-releases"), this);
releases->addFunction("tr", &Wt::WTemplate::Functions::tr);
auto search = new Wt::WLineEdit();
releases->bindWidget("search", search);
search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder"));
search->textInput().connect(std::bind([this, search]
{
auto keywords = splitString(search->text().toUTF8(), " ");
refresh(keywords);
}));
_search = new Wt::WLineEdit();
releases->bindWidget("search", _search);
_search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder"));
_search->textInput().connect(this, &Releases::refresh);
_releasesContainer = new Wt::WContainerWidget();
releases->bindWidget("releases", _releasesContainer);
refresh();
filters->updated().connect(std::bind([=] {
refresh();
}));
filters->updated().connect(this, &Releases::refresh);
}
void
Releases::refresh(std::vector<std::string> searchKeywords)
Releases::refresh()
{
auto searchKeywords = splitString(_search->text().toUTF8(), " ");
_releasesContainer->clear();
auto clusterIds = _filters->getClusterIds();
+3 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
namespace UserInterface {
@@ -34,9 +35,10 @@ class Releases : public Wt::WContainerWidget
Wt::Signal<Database::id_type> releasePlay;
private:
void refresh(std::vector<std::string> searchKeywords = std::vector<std::string>());
void refresh();
Filters* _filters;
Wt::WLineEdit* _search;
Wt::WContainerWidget* _releasesContainer;
};
+8 -12
View File
@@ -43,29 +43,25 @@ Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent)
auto tracks = new Wt::WTemplate(Wt::WString::tr("template-tracks"), this);
tracks->addFunction("tr", &Wt::WTemplate::Functions::tr);
auto search = new Wt::WLineEdit();
tracks->bindWidget("search", search);
search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder"));
search->textInput().connect(std::bind([this, search]
{
auto keywords = splitString(search->text().toUTF8(), " ");
refresh(keywords);
}));
_search = new Wt::WLineEdit();
tracks->bindWidget("search", _search);
_search->setPlaceholderText(Wt::WString::tr("msg-search-placeholder"));
_search->textInput().connect(this, &Tracks::refresh);
_tracksContainer = new Wt::WContainerWidget();
tracks->bindWidget("tracks", _tracksContainer);
refresh();
filters->updated().connect(std::bind([=] {
refresh();
}));
filters->updated().connect(this, &Tracks::refresh);
}
void
Tracks::refresh(std::vector<std::string> searchKeywords)
Tracks::refresh()
{
auto searchKeywords = splitString(_search->text().toUTF8(), " ");
_tracksContainer->clear();
auto clusterIds = _filters->getClusterIds();
+3 -1
View File
@@ -20,6 +20,7 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
namespace UserInterface {
@@ -33,9 +34,10 @@ class Tracks : public Wt::WContainerWidget
Wt::Signal<Database::id_type> trackPlay;
private:
void refresh(std::vector<std::string> searchKeywords = std::vector<std::string>());
void refresh();
Wt::WContainerWidget* _tracksContainer;
Wt::WLineEdit* _search;
Filters* _filters;
};