Changed the add button to a contextual menu

This commit is contained in:
emeric
2020-08-20 13:10:54 +02:00
parent 09f5ea5b0f
commit bc87e16d08
31 changed files with 304 additions and 94 deletions
+3 -3
View File
@@ -61,9 +61,9 @@ The Subsonic API is enabled by default.
__Note__: since _LMS_ stores hashed and salted passwords, it cannot handle the __token authentication__ method defined from version 1.13.0. __Note__: since _LMS_ stores hashed and salted passwords, it cannot handle the __token authentication__ method defined from version 1.13.0.
## Keyboard shortcuts ## Keyboard shortcuts
* Play/pause: Space * Play/pause: <kbd>Space</bbd>
* Previous track: Ctrl + Left * Previous track: <kbd>Ctrl</kbd> + <kbd>Left</kbd>
* Next track: Ctrl + Right * Next track: <kbd>Ctrl</kbd> + <kbd>Right</kbd>
## Installation ## Installation
+3 -1
View File
@@ -6,18 +6,20 @@
<div class="Lms-header"> <div class="Lms-header">
<h2><div class="Lms-artistname text-primary">${name}</div></h2> <h2><div class="Lms-artistname text-primary">${name}</div></h2>
${clusters} ${clusters}
${play-btn}${add-btn} ${play-btn}${more-btn}
</div> </div>
<div class="row"> <div class="row">
${releases class="Lms-explore-artist-entries-container"} ${releases class="Lms-explore-artist-entries-container"}
</div> </div>
<div class="row"> <div class="row">
${<if-has-similar-artists>}
<div class="col-md-4"> <div class="col-md-4">
<div class="well well-sm"> <div class="well well-sm">
<h3>${tr:Lms.Explore.Artist.similar-artists}</h3> <h3>${tr:Lms.Explore.Artist.similar-artists}</h3>
${similar-artists} ${similar-artists}
</div> </div>
</div> </div>
${</if-has-similar-artists>}
${<if-has-mbid>} ${<if-has-mbid>}
<div class="col-md-4"> <div class="col-md-4">
<div class="well well-sm"> <div class="well well-sm">
+1 -1
View File
@@ -5,7 +5,7 @@
${contents class="Lms-explore-contents"} ${contents class="Lms-explore-contents"}
</message> </message>
<message id="Lms.Explore.template.add-btn"><i class="fa fa-plus-circle Lms-explore-btn Lms-control-btn"></i></message> <message id="Lms.Explore.template.more-btn"><i class="fa fa-plus-circle Lms-explore-btn Lms-control-btn"></i></message>
<message id="Lms.Explore.template.play-btn"><i class="fa fa-play-circle Lms-explore-btn Lms-control-btn"></i></message> <message id="Lms.Explore.template.play-btn"><i class="fa fa-play-circle Lms-explore-btn Lms-control-btn"></i></message>
<message id="Lms.Explore.template.filters"> <message id="Lms.Explore.template.filters">
+3 -4
View File
@@ -121,6 +121,8 @@
<message id="Lms.Explore.most-played">Most played</message> <message id="Lms.Explore.most-played">Most played</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
<message id="Lms.Explore.play-last">Play last</message>
<message id="Lms.Explore.play-shuffled">Play shuffled</message>
<message id="Lms.Explore.random">Random</message> <message id="Lms.Explore.random">Random</message>
<message id="Lms.Explore.recently-added">Recently added</message> <message id="Lms.Explore.recently-added">Recently added</message>
<message id="Lms.Explore.recently-played">Recently played</message> <message id="Lms.Explore.recently-played">Recently played</message>
@@ -157,11 +159,8 @@
<plural case="0">Added {1} track</plural> <plural case="0">Added {1} track</plural>
<plural case="1">Added {1} tracks</plural> <plural case="1">Added {1} tracks</plural>
</message> </message>
<message id="Lms.PlayQueue.nb-tracks-playing">
<plural case="0">Playing {1} track</plural>
<plural case="1">Playing {1} tracks</plural>
</message>
<message id="Lms.PlayQueue.playqueue">Play Queue</message> <message id="Lms.PlayQueue.playqueue">Play Queue</message>
<message id="Lms.PlayQueue.playqueue-full">Play Queue full!</message>
<message id="Lms.PlayQueue.radio-mode">Radio mode</message> <message id="Lms.PlayQueue.radio-mode">Radio mode</message>
<message id="Lms.PlayQueue.repeat">Repeat</message> <message id="Lms.PlayQueue.repeat">Repeat</message>
<message id="Lms.PlayQueue.shuffle">Shuffle</message> <message id="Lms.PlayQueue.shuffle">Shuffle</message>
+3 -4
View File
@@ -121,6 +121,8 @@
<message id="Lms.Explore.most-played">Plus joués</message> <message id="Lms.Explore.most-played">Plus joués</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message> <message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message> <message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
<message id="Lms.Explore.play-last">Jouer en dernier</message>
<message id="Lms.Explore.play-shuffled">Jouer aléatoirement</message>
<message id="Lms.Explore.random">Aléatoire</message> <message id="Lms.Explore.random">Aléatoire</message>
<message id="Lms.Explore.recently-added">Ajoutés récemment</message> <message id="Lms.Explore.recently-added">Ajoutés récemment</message>
<message id="Lms.Explore.recently-played">Joués récemment</message> <message id="Lms.Explore.recently-played">Joués récemment</message>
@@ -157,11 +159,8 @@
<plural case="0">{1} piste ajoutée</plural> <plural case="0">{1} piste ajoutée</plural>
<plural case="1">{1} pistes ajoutées</plural> <plural case="1">{1} pistes ajoutées</plural>
</message> </message>
<message id="Lms.PlayQueue.nb-tracks-playing">
<plural case="0">{1} piste ajoutée</plural>
<plural case="1">{1} pistes ajoutées</plural>
</message>
<message id="Lms.PlayQueue.playqueue">Liste de lecture</message> <message id="Lms.PlayQueue.playqueue">Liste de lecture</message>
<message id="Lms.PlayQueue.playqueue-full">Liste de lecture pleine!</message>
<message id="Lms.PlayQueue.radio-mode">Mode radio</message> <message id="Lms.PlayQueue.radio-mode">Mode radio</message>
<message id="Lms.PlayQueue.repeat">Repéter</message> <message id="Lms.PlayQueue.repeat">Repéter</message>
<message id="Lms.PlayQueue.shuffle">Mélanger</message> <message id="Lms.PlayQueue.shuffle">Mélanger</message>
+4 -2
View File
@@ -17,12 +17,13 @@
${<if-has-artist>}<h4>${artist class="Lms-artistname text-muted"}</h4>${</if-has-artist>} ${<if-has-artist>}<h4>${artist class="Lms-artistname text-muted"}</h4>${</if-has-artist>}
<div class="text-muted"><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></div> <div class="text-muted"><small>${<if-has-year>}${year}${</if-has-year>}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>}</small></div>
${clusters} ${clusters}
${play-btn}${add-btn} ${play-btn}${more-btn}
</div> </div>
${container} ${container}
</div> </div>
</div> </div>
<div class="row"> <div class="row">
${<if-has-similar-releases>}
<div class="col-xs-12 col-md-8"> <div class="col-xs-12 col-md-8">
<div class="well well-sm"> <div class="well well-sm">
<h3>${tr:Lms.Explore.Release.similar-releases}</h3> <h3>${tr:Lms.Explore.Release.similar-releases}</h3>
@@ -31,6 +32,7 @@
</div> </div>
</div> </div>
</div> </div>
${</if-has-similar-releases>}
${<if-has-copyright>} ${<if-has-copyright>}
<div class="col-xs-12 col-md-4"> <div class="col-xs-12 col-md-4">
<div class="well well-sm"> <div class="well well-sm">
@@ -66,7 +68,7 @@
</div> </div>
<div class="col-xs-3"> <div class="col-xs-3">
<div class="Lms-explore-release-entry-controls"> <div class="Lms-explore-release-entry-controls">
${play-btn}${add-btn} ${play-btn}${more-btn}
</div> </div>
<div class="Lms-explore-release-entry-track-duration text-muted"> <div class="Lms-explore-release-entry-track-duration text-muted">
${duration} ${duration}
+1 -1
View File
@@ -6,7 +6,7 @@
<div class="Lms-header"> <div class="Lms-header">
<h2>${tr:Lms.Explore.releases}</h2> <h2>${tr:Lms.Explore.releases}</h2>
${mode class="nav nav-pills Lms-explore-mode-container"} ${mode class="nav nav-pills Lms-explore-mode-container"}
${play-btn}${add-btn} ${play-btn}${more-btn}
</div> </div>
<div class="row"> <div class="row">
${releases} ${releases}
+2 -2
View File
@@ -9,7 +9,7 @@
<div class="Lms-header"> <div class="Lms-header">
<h2>${tr:Lms.Explore.tracks}</h2> <h2>${tr:Lms.Explore.tracks}</h2>
${mode class="nav nav-pills Lms-explore-mode-container"} ${mode class="nav nav-pills Lms-explore-mode-container"}
${play-btn}${add-btn} ${play-btn}${more-btn}
</div> </div>
${tracks} ${tracks}
${loading-indicator class="Lms-horizontal-center Lms-loading-indicator"} ${loading-indicator class="Lms-horizontal-center Lms-loading-indicator"}
@@ -33,7 +33,7 @@
</div> </div>
<div class="col-xs-2 col-sm-2 col-md-2"> <div class="col-xs-2 col-sm-2 col-md-2">
<div class="Lms-explore-tracks-entry-controls"> <div class="Lms-explore-tracks-entry-controls">
${play-btn}${add-btn} ${play-btn}${more-btn}
</div> </div>
<div class="Lms-explore-tracks-entry-duration text-muted"> <div class="Lms-explore-tracks-entry-duration text-muted">
${duration} ${duration}
@@ -39,7 +39,6 @@ namespace Av
static constexpr std::size_t _chunkSize {262144}; static constexpr std::size_t _chunkSize {262144};
const std::filesystem::path _trackPath; const std::filesystem::path _trackPath;
Transcoder _transcoder; Transcoder _transcoder;
}; };
} }
+1
View File
@@ -23,6 +23,7 @@ add_executable(lms
ui/explore/Explore.cpp ui/explore/Explore.cpp
ui/explore/Filters.cpp ui/explore/Filters.cpp
ui/explore/ReleaseListHelpers.cpp ui/explore/ReleaseListHelpers.cpp
ui/explore/ReleasePopup.cpp
ui/explore/ReleasesView.cpp ui/explore/ReleasesView.cpp
ui/explore/ReleaseView.cpp ui/explore/ReleaseView.cpp
ui/explore/SearchView.cpp ui/explore/SearchView.cpp
+9 -5
View File
@@ -35,7 +35,6 @@
#include "database/User.hpp" #include "database/User.hpp"
#include "explore/Explore.hpp" #include "explore/Explore.hpp"
#include "explore/Filters.hpp" #include "explore/Filters.hpp"
#include "explore/SearchView.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/Service.hpp" #include "utils/Service.hpp"
#include "utils/String.hpp" #include "utils/String.hpp"
@@ -308,6 +307,13 @@ LmsApplication::createCluster(Database::Cluster::pointer cluster, bool canDelete
return res; return res;
} }
Wt::WPopupMenu*
LmsApplication::createPopupMenu()
{
_popupMenu = std::make_unique<Wt::WPopupMenu>();
return _popupMenu.get();
}
void void
LmsApplication::handleException(LmsApplicationException& e) LmsApplication::handleException(LmsApplicationException& e)
{ {
@@ -334,7 +340,6 @@ enum IdxRoot
{ {
IdxExplore = 0, IdxExplore = 0,
IdxPlayQueue, IdxPlayQueue,
IdxSearch,
IdxSettings, IdxSettings,
IdxAdminDatabase, IdxAdminDatabase,
IdxAdminUsers, IdxAdminUsers,
@@ -355,9 +360,9 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin)
{ "/artist", IdxExplore, false }, { "/artist", IdxExplore, false },
{ "/releases", IdxExplore, false }, { "/releases", IdxExplore, false },
{ "/release", IdxExplore, false }, { "/release", IdxExplore, false },
{ "/search", IdxExplore, false },
{ "/tracks", IdxExplore, false }, { "/tracks", IdxExplore, false },
{ "/playqueue", IdxPlayQueue, false }, { "/playqueue", IdxPlayQueue, false },
{ "/search", IdxSearch, false },
{ "/settings", IdxSettings, false }, { "/settings", IdxSettings, false },
{ "/admin/database", IdxAdminDatabase, true }, { "/admin/database", IdxAdminDatabase, true },
{ "/admin/users", IdxAdminUsers, true }, { "/admin/users", IdxAdminUsers, true },
@@ -471,7 +476,6 @@ LmsApplication::createHome()
Explore* explore = mainStack->addNew<Explore>(filters); Explore* explore = mainStack->addNew<Explore>(filters);
_playQueue = mainStack->addNew<PlayQueue>(); _playQueue = mainStack->addNew<PlayQueue>();
auto* search {mainStack->addNew<SearchView>(filters)};
mainStack->addNew<SettingsView>(); mainStack->addNew<SettingsView>();
searchEdit->enterPressed().connect([=] searchEdit->enterPressed().connect([=]
@@ -482,7 +486,7 @@ LmsApplication::createHome()
searchEdit->textInput().connect([=] searchEdit->textInput().connect([=]
{ {
setInternalPath("/search", true); setInternalPath("/search", true);
search->refreshView(searchEdit->text().toUTF8()); explore->search(searchEdit->text());
}); });
// Admin stuff // Admin stuff
+13 -10
View File
@@ -22,6 +22,7 @@
#include <optional> #include <optional>
#include <Wt/WApplication.h> #include <Wt/WApplication.h>
#include <Wt/WPopupMenu.h>
#include "database/Db.hpp" #include "database/Db.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
@@ -101,6 +102,7 @@ class LmsApplication : public Wt::WApplication
static Wt::WLink createReleaseLink(Wt::Dbo::ptr<Database::Release> release); static Wt::WLink createReleaseLink(Wt::Dbo::ptr<Database::Release> release);
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Wt::Dbo::ptr<Database::Release> release, bool addText = true); static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Wt::Dbo::ptr<Database::Release> release, bool addText = true);
static std::unique_ptr<Wt::WText> createCluster(Wt::Dbo::ptr<Database::Cluster> cluster, bool canDelete = false); static std::unique_ptr<Wt::WText> createCluster(Wt::Dbo::ptr<Database::Cluster> cluster, bool canDelete = false);
Wt::WPopupMenu* createPopupMenu();
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; } MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
PlayQueue& getPlayQueue() const { return *_playQueue; } PlayQueue& getPlayQueue() const { return *_playQueue; }
@@ -124,17 +126,18 @@ class LmsApplication : public Wt::WApplication
void createHome(); void createHome();
Wt::Signal<> _preQuit; Wt::Signal<> _preQuit;
Database::Session _dbSession; Database::Session _dbSession;
LmsApplicationGroupContainer& _appGroups; LmsApplicationGroupContainer& _appGroups;
Events _events; Events _events;
std::optional<Database::IdType> _userId; std::optional<Database::IdType> _userId;
std::optional<bool> _userAuthStrong; std::optional<bool> _userAuthStrong;
std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource; std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource;
std::shared_ptr<AudioFileResource> _audioFileResource; std::shared_ptr<AudioFileResource> _audioFileResource;
std::shared_ptr<ImageResource> _imageResource; std::shared_ptr<ImageResource> _imageResource;
MediaPlayer* _mediaPlayer {}; MediaPlayer* _mediaPlayer {};
PlayQueue* _playQueue {}; PlayQueue* _playQueue {};
std::unique_ptr<Wt::WPopupMenu> _popupMenu {};
}; };
+44 -15
View File
@@ -209,6 +209,13 @@ PlayQueue::getTrackList() const
return Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId); return Database::TrackList::getById(LmsApp->getDbSession(), _tracklistId);
} }
bool
PlayQueue::isFull() const
{
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
return getTrackList()->getCount() == _nbMaxEntries;
}
void void
PlayQueue::clearTracks() PlayQueue::clearTracks()
{ {
@@ -321,55 +328,77 @@ PlayQueue::updateCurrentTrack(bool selected)
entry->bindString("is-selected", selected ? "Lms-playqueue-selected" : ""); entry->bindString("is-selected", selected ? "Lms-playqueue-selected" : "");
} }
void std::size_t
PlayQueue::enqueueTracks(const std::vector<Database::IdType>& trackIds) PlayQueue::enqueueTracks(const std::vector<Database::IdType>& trackIds)
{ {
std::size_t nbTracksQueued {};
{ {
auto transaction {LmsApp->getDbSession().createUniqueTransaction()}; auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto tracklist = getTrackList(); auto tracklist {getTrackList()};
std::size_t nbTracksToEnqueue {tracklist->getCount() + trackIds.size() > _nbMaxEntries ? _nbMaxEntries - tracklist->getCount() : trackIds.size()};
for (Database::IdType trackId : trackIds) for (Database::IdType trackId : trackIds)
{ {
Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)}; Database::Track::pointer track {Database::Track::getById(LmsApp->getDbSession(), trackId)};
if (!track) if (!track)
continue; continue;
if (nbTracksQueued == nbTracksToEnqueue)
break;
Database::TrackListEntry::create(LmsApp->getDbSession(), track, tracklist); Database::TrackListEntry::create(LmsApp->getDbSession(), track, tracklist);
nbTracksQueued++;
} }
} }
updateInfo(); updateInfo();
addSome(); addSome();
}
void return nbTracksQueued;
PlayQueue::enqueueTrack(Database::IdType trackId)
{
enqueueTracks({trackId});
} }
void void
PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::IdType>& trackIds) PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::IdType>& trackIds)
{ {
std::size_t nbAddedTracks {};
switch (action) switch (action)
{ {
case PlayQueueAction::AddLast: case PlayQueueAction::PlayLast:
enqueueTracks(trackIds); nbAddedTracks = enqueueTracks(trackIds);
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000)); if (!_trackPos)
break; loadTrack(0, true);
case PlayQueueAction::AddNext:
break; break;
case PlayQueueAction::Play: case PlayQueueAction::Play:
clearTracks(); clearTracks();
enqueueTracks(trackIds); nbAddedTracks = enqueueTracks(trackIds);
loadTrack(0, true); loadTrack(0, true);
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-playing", trackIds.size()).arg(trackIds.size()), std::chrono::milliseconds(2000));
break; break;
case PlayQueueAction::PlayShuffled:
{
clearTracks();
{
std::vector<Database::IdType> shuffledTrackIds {trackIds};
Random::shuffleContainer(shuffledTrackIds);
nbAddedTracks = enqueueTracks(shuffledTrackIds);
}
loadTrack(0, true);
break;
}
} }
if (nbAddedTracks > 0)
LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", nbAddedTracks).arg(nbAddedTracks), std::chrono::milliseconds(2000));
if (isFull())
LmsApp->notifyMsg(MsgType::Warning, Wt::WString::tr("Lms.PlayQueue.playqueue-full"), std::chrono::milliseconds(2000));
} }
void void
+4 -2
View File
@@ -60,10 +60,10 @@ class PlayQueue : public Wt::WTemplate
private: private:
Wt::Dbo::ptr<Database::TrackList> getTrackList() const; Wt::Dbo::ptr<Database::TrackList> getTrackList() const;
bool isFull() const;
void clearTracks(); void clearTracks();
void enqueueTracks(const std::vector<Database::IdType>& trackIds); std::size_t enqueueTracks(const std::vector<Database::IdType>& trackIds);
void enqueueTrack(Database::IdType trackId);
void addSome(); void addSome();
void enqueueRadioTrack(); void enqueueRadioTrack();
void updateInfo(); void updateInfo();
@@ -80,6 +80,8 @@ class PlayQueue : public Wt::WTemplate
void addRadioTrackFromClusters(); void addRadioTrackFromClusters();
std::optional<float> getReplayGain(std::size_t pos, const Wt::Dbo::ptr<Database::Track>& track) const; std::optional<float> getReplayGain(std::size_t pos, const Wt::Dbo::ptr<Database::Track>& track) const;
static inline constexpr std::size_t _nbMaxEntries {1000};
bool _repeatAll {}; bool _repeatAll {};
bool _radioMode {}; bool _radioMode {};
bool _mediaPlayerSettingsLoaded {}; bool _mediaPlayerSettingsLoaded {};
+9 -2
View File
@@ -19,15 +19,22 @@
#pragma once #pragma once
#include <vector>
#include <Wt/WSignal.h>
#include "database/Types.hpp"
namespace UserInterface namespace UserInterface
{ {
enum class PlayQueueAction enum class PlayQueueAction
{ {
Play, Play,
AddNext, PlayLast,
AddLast, PlayShuffled,
}; };
using PlayQueueActionSignal = Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&>;
} }
+20 -3
View File
@@ -115,11 +115,24 @@ Artist::refreshView()
} }
{ {
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML); Wt::WText* moreBtn = bindNew<Wt::WText>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML);
addBtn->clicked().connect([=] moreBtn->clicked().connect([=]
{ {
artistsAction.emit(PlayQueueAction::AddLast, {*artistId}); Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
popup->addItem(Wt::WString::tr("Lms.Explore.play-shuffled"))
->triggered().connect(this, [=]
{
artistsAction.emit(PlayQueueAction::PlayShuffled, {*artistId});
});
popup->addItem(Wt::WString::tr("Lms.Explore.play-last"))
->triggered().connect(this, [=]
{
artistsAction.emit(PlayQueueAction::PlayLast, {*artistId});
});
popup->exec(moreBtn);
}); });
} }
@@ -135,6 +148,10 @@ Artist::refreshView()
void void
Artist::refreshSimilarArtists(const std::vector<Database::IdType>& similarArtistsId) Artist::refreshSimilarArtists(const std::vector<Database::IdType>& similarArtistsId)
{ {
if (similarArtistsId.empty())
return;
setCondition("if-has-similar-artists", true);
Wt::WContainerWidget* similarArtistsContainer {bindNew<Wt::WContainerWidget>("similar-artists")}; Wt::WContainerWidget* similarArtistsContainer {bindNew<Wt::WContainerWidget>("similar-artists")};
for (Database::IdType artistId : similarArtistsId) for (Database::IdType artistId : similarArtistsId)
+1 -1
View File
@@ -42,7 +42,7 @@ class Artist : public Wt::WTemplate
public: public:
Artist(Filters* filters); Artist(Filters* filters);
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> artistsAction; PlayQueueActionSignal artistsAction;
private: private:
void refreshView(); void refreshView();
+14
View File
@@ -35,6 +35,7 @@
#include "Filters.hpp" #include "Filters.hpp"
#include "ReleasesView.hpp" #include "ReleasesView.hpp"
#include "ReleaseView.hpp" #include "ReleaseView.hpp"
#include "SearchView.hpp"
#include "TracksView.hpp" #include "TracksView.hpp"
namespace UserInterface { namespace UserInterface {
@@ -50,6 +51,7 @@ handleContentsPathChange(Wt::WStackedWidget* stack)
IdxArtist, IdxArtist,
IdxReleases, IdxReleases,
IdxRelease, IdxRelease,
IdxSearch,
IdxTracks, IdxTracks,
}; };
@@ -59,6 +61,7 @@ handleContentsPathChange(Wt::WStackedWidget* stack)
{ "/artist", IdxArtist }, { "/artist", IdxArtist },
{ "/releases", IdxReleases }, { "/releases", IdxReleases },
{ "/release", IdxRelease }, { "/release", IdxRelease },
{ "/search", IdxSearch },
{ "/tracks", IdxTracks }, { "/tracks", IdxTracks },
}; };
@@ -100,6 +103,11 @@ Explore::Explore(Filters* filters)
release->tracksAction.connect(this, &Explore::handleTracksAction); release->tracksAction.connect(this, &Explore::handleTracksAction);
contentsStack->addWidget(std::move(release)); contentsStack->addWidget(std::move(release));
auto search = std::make_unique<SearchView>(_filters);
search->tracksAction.connect(this, &Explore::handleTracksAction);
_search = search.get();
contentsStack->addWidget(std::move(search));
auto tracks = std::make_unique<Tracks>(_filters); auto tracks = std::make_unique<Tracks>(_filters);
tracks->tracksAction.connect(this, &Explore::handleTracksAction); tracks->tracksAction.connect(this, &Explore::handleTracksAction);
contentsStack->addWidget(std::move(tracks)); contentsStack->addWidget(std::move(tracks));
@@ -112,6 +120,12 @@ Explore::Explore(Filters* filters)
handleContentsPathChange(contentsStack); handleContentsPathChange(contentsStack);
} }
void
Explore::search(const Wt::WString& searchText)
{
_search->refreshView(searchText);
}
static static
std::vector<Database::IdType> std::vector<Database::IdType>
getArtistsTracks(Database::Session& session, const std::vector<Database::IdType>& artistsId, const std::set<Database::IdType>&) getArtistsTracks(Database::Session& session, const std::vector<Database::IdType>& artistsId, const std::set<Database::IdType>&)
+5 -1
View File
@@ -27,13 +27,16 @@
namespace UserInterface { namespace UserInterface {
class Filters; class Filters;
class SearchView;
class Explore : public Wt::WTemplate class Explore : public Wt::WTemplate
{ {
public: public:
Explore(Filters* filters); Explore(Filters* filters);
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> tracksAction; void search(const Wt::WString& searchText);
PlayQueueActionSignal tracksAction;
private: private:
@@ -42,6 +45,7 @@ class Explore : public Wt::WTemplate
void handleTracksAction(PlayQueueAction action, const std::vector<Database::IdType>& tracksId); void handleTracksAction(PlayQueueAction action, const std::vector<Database::IdType>& tracksId);
Filters* _filters {}; Filters* _filters {};
SearchView* _search {};
}; };
} // namespace UserInterface } // namespace UserInterface
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright (C) 2020 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ReleasePopup.hpp"
#include "LmsApplication.hpp"
namespace UserInterface
{
void
displayReleasePopupMenu(Wt::WInteractWidget& target,
Database::IdType releaseId,
PlayQueueActionSignal& releasesAction)
{
Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
popup->addItem(Wt::WString::tr("Lms.Explore.play-shuffled"))
->triggered().connect(&target, [&releasesAction, releaseId]
{
releasesAction.emit(PlayQueueAction::PlayShuffled, {releaseId});
});
popup->addItem(Wt::WString::tr("Lms.Explore.play-last"))
->triggered().connect(&target, [&releasesAction, releaseId]
{
releasesAction.emit(PlayQueueAction::PlayLast, {releaseId});
});
popup->popup(&target);
}
} // namespace UserInterface
+34
View File
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2020 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WWidget.h>
#include <Wt/WSignal.h>
#include "database/Types.hpp"
#include "PlayQueueAction.hpp"
namespace UserInterface
{
void displayReleasePopupMenu(Wt::WInteractWidget& target,
Database::IdType releaseId,
PlayQueueActionSignal& releasesAction);
} // namespace UserInterface
+19 -7
View File
@@ -22,7 +22,6 @@
#include <Wt/WAnchor.h> #include <Wt/WAnchor.h>
#include <Wt/WApplication.h> #include <Wt/WApplication.h>
#include <Wt/WImage.h> #include <Wt/WImage.h>
#include <Wt/WPopupMenu.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include <Wt/WText.h> #include <Wt/WText.h>
@@ -39,6 +38,7 @@
#include "LmsApplicationException.hpp" #include "LmsApplicationException.hpp"
#include "MediaPlayer.hpp" #include "MediaPlayer.hpp"
#include "ReleaseListHelpers.hpp" #include "ReleaseListHelpers.hpp"
#include "ReleasePopup.hpp"
#include "TrackStringUtils.hpp" #include "TrackStringUtils.hpp"
using namespace Database; using namespace Database;
@@ -157,10 +157,10 @@ Release::refreshView()
} }
{ {
Wt::WText* addBtn {bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)}; Wt::WText* moreBtn {bindNew<Wt::WText>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)};
addBtn->clicked().connect([=] moreBtn->clicked().connect([=]
{ {
releasesAction.emit(PlayQueueAction::AddLast, {*releaseId}); displayReleasePopupMenu(*moreBtn, *releaseId, releasesAction);
}); });
} }
@@ -237,10 +237,18 @@ Release::refreshView()
tracksAction.emit(PlayQueueAction::Play, {trackId}); tracksAction.emit(PlayQueueAction::Play, {trackId});
}); });
Wt::WText* addBtn {entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)}; Wt::WText* moreBtn {entry->bindNew<Wt::WText>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)};
addBtn->clicked().connect([=]() moreBtn->clicked().connect([=]()
{ {
tracksAction.emit(PlayQueueAction::AddLast, {trackId}); Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
popup->addItem(Wt::WString::tr("Lms.Explore.play-last"))
->triggered().connect(moreBtn, [=]
{
tracksAction.emit(PlayQueueAction::PlayLast, {trackId});
});
popup->popup(moreBtn);
}); });
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain); entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
@@ -306,6 +314,10 @@ Release::refreshLinks(const Database::Release::pointer& release)
void void
Release::refreshSimilarReleases(const std::vector<Database::IdType>& similarReleasesId) Release::refreshSimilarReleases(const std::vector<Database::IdType>& similarReleasesId)
{ {
if (similarReleasesId.empty())
return;
setCondition("if-has-similar-releases", true);
auto* similarReleasesContainer {bindNew<Wt::WContainerWidget>("similar-releases")}; auto* similarReleasesContainer {bindNew<Wt::WContainerWidget>("similar-releases")};
for (Database::IdType id : similarReleasesId) for (Database::IdType id : similarReleasesId)
+2 -6
View File
@@ -19,7 +19,6 @@
#pragma once #pragma once
#include <Wt/WSignal.h>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "database/Types.hpp" #include "database/Types.hpp"
@@ -39,11 +38,8 @@ namespace UserInterface
public: public:
Release(Filters* filters); Release(Filters* filters);
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> releasesAction; PlayQueueActionSignal releasesAction;
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> tracksAction; PlayQueueActionSignal tracksAction;
Wt::Signal<Wt::WWidget&, Database::IdType> createReleaseActionMenu;
Wt::Signal<Wt::WWidget&, Database::IdType> createTrackActionMenu;
private: private:
void refreshView(); void refreshView();
+16 -3
View File
@@ -72,10 +72,23 @@ _filters {filters}
{ {
releasesAction.emit(PlayQueueAction::Play, getAllReleases()); releasesAction.emit(PlayQueueAction::Play, getAllReleases());
}); });
Wt::WText* addBtn {bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML)}; Wt::WText* moreBtn {bindNew<Wt::WText>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)};
addBtn->clicked().connect([this] moreBtn->clicked().connect([=]
{ {
releasesAction.emit(PlayQueueAction::AddLast, getAllReleases()); Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
popup->addItem(Wt::WString::tr("Lms.Explore.play-shuffled"))
->triggered().connect([this]
{
releasesAction.emit(PlayQueueAction::PlayShuffled, getAllReleases());
});
popup->addItem(Wt::WString::tr("Lms.Explore.play-last"))
->triggered().connect([this]
{
releasesAction.emit(PlayQueueAction::PlayLast, getAllReleases());
});
popup->popup(moreBtn);
}); });
_container = bindNew<Wt::WContainerWidget>("releases"); _container = bindNew<Wt::WContainerWidget>("releases");
+1 -1
View File
@@ -41,7 +41,7 @@ class Releases : public Wt::WTemplate
public: public:
Releases(Filters* filters); Releases(Filters* filters);
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> releasesAction; PlayQueueActionSignal releasesAction;
private: private:
+3 -3
View File
@@ -51,9 +51,9 @@ namespace UserInterface
} }
void void
SearchView::refreshView(const std::string& searchText) SearchView::refreshView(const Wt::WString& searchText)
{ {
_keywords = StringUtils::splitString(searchText, " "); _keywords = StringUtils::splitString(searchText.toUTF8(), " ");
refreshView(); refreshView();
} }
@@ -127,7 +127,7 @@ namespace UserInterface
auto* container {bindNew<Wt::WContainerWidget>("tracks")}; auto* container {bindNew<Wt::WContainerWidget>("tracks")};
for (const Database::Track::pointer& track : tracks) for (const Database::Track::pointer& track : tracks)
container->addWidget(TrackListHelpers::createEntry(track)); container->addWidget(TrackListHelpers::createEntry(track, tracksAction));
} }
} }
+5 -1
View File
@@ -24,6 +24,8 @@
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "PlayQueueAction.hpp"
namespace UserInterface { namespace UserInterface {
class Filters; class Filters;
@@ -33,7 +35,9 @@ class SearchView : public Wt::WTemplate
public: public:
SearchView(Filters* filters); SearchView(Filters* filters);
void refreshView(const std::string& searchText); PlayQueueActionSignal tracksAction;
void refreshView(const Wt::WString& searchText);
private: private:
void refreshView(); void refreshView();
+14 -7
View File
@@ -30,7 +30,6 @@
#include "resource/ImageResource.hpp" #include "resource/ImageResource.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
#include "MediaPlayer.hpp" #include "MediaPlayer.hpp"
#include "PlayQueue.hpp"
#include "TrackStringUtils.hpp" #include "TrackStringUtils.hpp"
using namespace Database; using namespace Database;
@@ -38,7 +37,7 @@ using namespace Database;
namespace UserInterface::TrackListHelpers namespace UserInterface::TrackListHelpers
{ {
std::unique_ptr<Wt::WTemplate> std::unique_ptr<Wt::WTemplate>
createEntry(const Wt::Dbo::ptr<Database::Track>& track) createEntry(const Wt::Dbo::ptr<Database::Track>& track, PlayQueueActionSignal& tracksAction)
{ {
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))}; auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
auto* entryPtr {entry.get()}; auto* entryPtr {entry.get()};
@@ -89,15 +88,23 @@ namespace UserInterface::TrackListHelpers
entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain); entry->bindString("duration", trackDurationToString(track->getDuration()), Wt::TextFormat::Plain);
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML); Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML);
playBtn->clicked().connect([=] playBtn->clicked().connect([trackId, &tracksAction]
{ {
LmsApp->getPlayQueue().processTracks(PlayQueueAction::Play, {trackId}); tracksAction.emit(PlayQueueAction::Play, {trackId});
}); });
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML); Wt::WText* moreBtn = entry->bindNew<Wt::WText>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML);
addBtn->clicked().connect([=] moreBtn->clicked().connect([=, &tracksAction]
{ {
LmsApp->getPlayQueue().processTracks(PlayQueueAction::AddLast, {trackId}); Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
popup->addItem(Wt::WString::tr("Lms.Explore.play-last"))
->triggered().connect(moreBtn, [=, &tracksAction]
{
tracksAction.emit(PlayQueueAction::PlayLast, {trackId});
});
popup->popup(moreBtn);
}); });
LmsApp->getMediaPlayer().trackLoaded.connect(entryPtr, [=] (Database::IdType loadedTrackId) LmsApp->getMediaPlayer().trackLoaded.connect(entryPtr, [=] (Database::IdType loadedTrackId)
+2 -1
View File
@@ -22,6 +22,7 @@
#include <memory> #include <memory>
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
#include "PlayQueueAction.hpp"
namespace Database namespace Database
{ {
@@ -30,6 +31,6 @@ namespace Database
namespace UserInterface::TrackListHelpers namespace UserInterface::TrackListHelpers
{ {
std::unique_ptr<Wt::WTemplate> createEntry(const Wt::Dbo::ptr<Database::Track>& track); std::unique_ptr<Wt::WTemplate> createEntry(const Wt::Dbo::ptr<Database::Track>& track, PlayQueueActionSignal& tracksAction);
} // namespace UserInterface } // namespace UserInterface
+18 -6
View File
@@ -32,9 +32,8 @@
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/String.hpp" #include "utils/String.hpp"
#include "resource/ImageResource.hpp"
#include "common/LoadingIndicator.hpp" #include "common/LoadingIndicator.hpp"
#include "resource/ImageResource.hpp"
#include "Filters.hpp" #include "Filters.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
#include "MediaPlayer.hpp" #include "MediaPlayer.hpp"
@@ -76,10 +75,23 @@ _filters {filters}
tracksAction.emit(PlayQueueAction::Play, getAllTracks()); tracksAction.emit(PlayQueueAction::Play, getAllTracks());
}); });
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.template.add-btn"), Wt::TextFormat::XHTML); Wt::WText* moreBtn = bindNew<Wt::WText>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML);
addBtn->clicked().connect([=] moreBtn->clicked().connect([=]
{ {
tracksAction.emit(PlayQueueAction::AddLast, getAllTracks()); Wt::WPopupMenu* popup {LmsApp->createPopupMenu()};
popup->addItem(Wt::WString::tr("Lms.Explore.play-shuffled"))
->triggered().connect(this, [this]
{
tracksAction.emit(PlayQueueAction::PlayShuffled, getAllTracks());
});
popup->addItem(Wt::WString::tr("Lms.Explore.play-last"))
->triggered().connect(this, [this]
{
tracksAction.emit(PlayQueueAction::PlayLast, getAllTracks());
});
popup->popup(moreBtn);
}); });
_tracksContainer = bindNew<Wt::WContainerWidget>("tracks"); _tracksContainer = bindNew<Wt::WContainerWidget>("tracks");
@@ -219,7 +231,7 @@ Tracks::addSome()
bool moreResults; bool moreResults;
for (const Track::pointer& track : getTracks(Range {static_cast<std::size_t>(_tracksContainer->count()), batchSize}, moreResults)) for (const Track::pointer& track : getTracks(Range {static_cast<std::size_t>(_tracksContainer->count()), batchSize}, moreResults))
{ {
_tracksContainer->addWidget(TrackListHelpers::createEntry(track)); _tracksContainer->addWidget(TrackListHelpers::createEntry(track, tracksAction));
} }
if (moreResults) if (moreResults)
+1 -1
View File
@@ -40,7 +40,7 @@ class Tracks : public Wt::WTemplate
public: public:
Tracks(Filters* filters); Tracks(Filters* filters);
Wt::Signal<PlayQueueAction, const std::vector<Database::IdType>&> tracksAction; PlayQueueActionSignal tracksAction;
private: private: