Added a message box to display some track information. closes #276
This commit is contained in:
@@ -23,6 +23,7 @@ extern "C"
|
||||
{
|
||||
#define __STDC_CONSTANT_MACROS
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/codec_par.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libavutil/error.h>
|
||||
}
|
||||
@@ -38,7 +39,7 @@ static std::string averror_to_string(int error)
|
||||
{
|
||||
std::array<char, 128> buf = {0};
|
||||
|
||||
if (av_strerror(error, buf.data(), buf.size()) == 0)
|
||||
if (::av_strerror(error, buf.data(), buf.size()) == 0)
|
||||
return &buf[0];
|
||||
else
|
||||
return "Unknown error";
|
||||
@@ -104,7 +105,7 @@ getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& re
|
||||
return;
|
||||
|
||||
AVDictionaryEntry *tag = NULL;
|
||||
while ((tag = av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
|
||||
while ((tag = ::av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX)))
|
||||
{
|
||||
res[StringUtils::stringToUpper(tag->key)] = tag->value;
|
||||
}
|
||||
@@ -140,31 +141,18 @@ AudioFile::getStreamInfo() const
|
||||
|
||||
for (std::size_t i {}; i < _context->nb_streams; ++i)
|
||||
{
|
||||
AVStream* avstream { _context->streams[i]};
|
||||
|
||||
// Skip attached pics
|
||||
if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
|
||||
continue;
|
||||
|
||||
if (!avstream->codecpar)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codecpar is set";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (avstream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
|
||||
continue;
|
||||
|
||||
res.push_back( {i, static_cast<std::size_t>(avstream->codecpar->bit_rate)} );
|
||||
std::optional<StreamInfo> streamInfo {getStreamInfo(i)};
|
||||
if (streamInfo)
|
||||
res.emplace_back(std::move(*streamInfo));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<std::size_t>
|
||||
AudioFile::getBestStream() const
|
||||
AudioFile::getBestStreamIndex() const
|
||||
{
|
||||
int res = av_find_best_stream(_context,
|
||||
int res = ::av_find_best_stream(_context,
|
||||
AVMEDIA_TYPE_AUDIO,
|
||||
-1, // Auto
|
||||
-1, // Auto
|
||||
@@ -177,6 +165,18 @@ AudioFile::getBestStream() const
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<StreamInfo>
|
||||
AudioFile::getBestStreamInfo() const
|
||||
{
|
||||
std::optional<StreamInfo> res;
|
||||
|
||||
std::optional<std::size_t> bestStreamIndex {getBestStreamIndex()};
|
||||
if (bestStreamIndex)
|
||||
res = getStreamInfo(*bestStreamIndex);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
AudioFile::hasAttachedPictures() const
|
||||
{
|
||||
@@ -238,10 +238,39 @@ AudioFile::visitAttachedPictures(std::function<void(const Picture&)> func) const
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<AudioFileFormat>
|
||||
guessMediaFileFormat(const std::filesystem::path& file)
|
||||
std::optional<StreamInfo>
|
||||
AudioFile::getStreamInfo(std::size_t streamIndex) const
|
||||
{
|
||||
const AVOutputFormat* format {av_guess_format(NULL,file.string().c_str(),NULL)};
|
||||
std::optional<StreamInfo> res;
|
||||
|
||||
AVStream* avstream { _context->streams[streamIndex]};
|
||||
assert(avstream);
|
||||
|
||||
if (avstream->disposition & AV_DISPOSITION_ATTACHED_PIC)
|
||||
return res;
|
||||
|
||||
if (!avstream->codecpar)
|
||||
{
|
||||
LMS_LOG(AV, ERROR) << "Skipping stream " << streamIndex << " since no codecpar is set";
|
||||
return res;
|
||||
}
|
||||
|
||||
if (avstream->codecpar->codec_type != AVMEDIA_TYPE_AUDIO)
|
||||
return res;
|
||||
|
||||
res.emplace();
|
||||
res->index = streamIndex;
|
||||
res->bitrate = static_cast<std::size_t>(avstream->codecpar->bit_rate);
|
||||
res->codec = ::avcodec_get_name(avstream->codecpar->codec_id);
|
||||
assert(!res->codec.empty());
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::optional<AudioFileFormat>
|
||||
guessAudioFileFormat(const std::filesystem::path& file)
|
||||
{
|
||||
const AVOutputFormat* format {::av_guess_format(NULL, file.string().c_str(), NULL)};
|
||||
if (!format || !format->name)
|
||||
return {};
|
||||
|
||||
|
||||
@@ -43,11 +43,14 @@ namespace Av
|
||||
std::chrono::milliseconds getDuration() const override;
|
||||
MetadataMap getMetaData() const override;
|
||||
std::vector<StreamInfo> getStreamInfo() const override;
|
||||
std::optional<std::size_t> getBestStream() const override;
|
||||
std::optional<StreamInfo> getBestStreamInfo() const override;
|
||||
std::optional<std::size_t> getBestStreamIndex() const override;
|
||||
bool hasAttachedPictures() const override;
|
||||
void visitAttachedPictures(std::function<void(const Picture&)> func) const override;
|
||||
|
||||
private:
|
||||
std::optional<StreamInfo> getStreamInfo(std::size_t streamIndex) const;
|
||||
|
||||
const std::filesystem::path _p;
|
||||
AVFormatContext* _context {};
|
||||
};
|
||||
|
||||
@@ -37,13 +37,14 @@ namespace Av
|
||||
{
|
||||
std::string mimeType;
|
||||
const std::byte* data {};
|
||||
std::size_t dataSize;
|
||||
std::size_t dataSize {};
|
||||
};
|
||||
|
||||
struct StreamInfo
|
||||
{
|
||||
size_t id;
|
||||
std::size_t bitrate;
|
||||
size_t index {};
|
||||
std::size_t bitrate {};
|
||||
std::string codec;
|
||||
};
|
||||
|
||||
class IAudioFile
|
||||
@@ -57,7 +58,8 @@ namespace Av
|
||||
virtual std::chrono::milliseconds getDuration() const = 0;
|
||||
virtual MetadataMap getMetaData() const = 0;
|
||||
virtual std::vector<StreamInfo> getStreamInfo() const = 0;
|
||||
virtual std::optional<std::size_t> getBestStream() const = 0; // none if failure/unknown
|
||||
virtual std::optional<StreamInfo> getBestStreamInfo() const = 0; // none if failure/unknown
|
||||
virtual std::optional<std::size_t> getBestStreamIndex() const = 0; // none if failure/unknown
|
||||
virtual bool hasAttachedPictures() const = 0;
|
||||
virtual void visitAttachedPictures(std::function<void(const Picture&)> func) const = 0;
|
||||
};
|
||||
|
||||
@@ -94,7 +94,10 @@ createQuery(Session& session, const Artist::FindParameters& params)
|
||||
session.checkSharedLocked();
|
||||
|
||||
auto query {session.getDboSession().query<ArtistId>("SELECT DISTINCT a.id FROM artist a")};
|
||||
if (params.sortMethod == ArtistSortMethod::LastWritten || params.writtenAfter.isValid() || params.linkType)
|
||||
if (params.sortMethod == ArtistSortMethod::LastWritten
|
||||
|| params.writtenAfter.isValid()
|
||||
|| params.linkType
|
||||
|| params.track.isValid())
|
||||
{
|
||||
query.join("track t ON t.id = t_a_l.track_id");
|
||||
query.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id");
|
||||
@@ -157,6 +160,11 @@ createQuery(Session& session, const Artist::FindParameters& params)
|
||||
query.where(oss.str());
|
||||
}
|
||||
|
||||
if (params.track.isValid())
|
||||
{
|
||||
query.where("t.id = ?").bind(params.track);
|
||||
}
|
||||
|
||||
switch (params.sortMethod)
|
||||
{
|
||||
case ArtistSortMethod::None:
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "services/database/ReleaseId.hpp"
|
||||
#include "services/database/Types.hpp"
|
||||
#include "services/database/UserId.hpp"
|
||||
#include "services/database/TrackId.hpp"
|
||||
#include "utils/EnumSet.hpp"
|
||||
#include "utils/UUID.hpp"
|
||||
|
||||
@@ -61,6 +62,7 @@ class Artist : public Object<Artist, ArtistId>
|
||||
Wt::WDateTime writtenAfter;
|
||||
UserId starringUser; // only artists starred by this user
|
||||
std::optional<Scrobbler> scrobbler; // and for this scrobbler
|
||||
TrackId track; // artists involved in this track
|
||||
|
||||
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
|
||||
@@ -69,6 +71,7 @@ class Artist : public Object<Artist, ArtistId>
|
||||
FindParameters& setRange(Range _range) {range = _range; return *this; }
|
||||
FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; }
|
||||
FindParameters& setStarringUser(UserId _user, Scrobbler _scrobbler) { starringUser = _user; scrobbler = _scrobbler; return *this; }
|
||||
FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
|
||||
};
|
||||
|
||||
Artist() = default;
|
||||
|
||||
@@ -92,21 +92,27 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist"))};
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
}
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo"))};
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist"))};
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto artists {Artist::find(session, Artist::FindParameters{}.setTrack(track->getId()))};
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
|
||||
@@ -198,7 +204,7 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
|
||||
auto tracks {Track::find(session, Track::FindParameters {}.setArtist(artist1->getId()))};
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
|
||||
@@ -278,71 +278,6 @@ LmsApplication::finalize()
|
||||
preQuit().emit();
|
||||
}
|
||||
|
||||
Wt::WLink
|
||||
LmsApplication::createArtistLink(Database::Artist::pointer artist)
|
||||
{
|
||||
if (const auto mbid {artist->getMBID()})
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()}};
|
||||
else
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString()};
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WAnchor>
|
||||
LmsApplication::createArtistAnchor(Database::Artist::pointer artist, bool addText)
|
||||
{
|
||||
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
|
||||
|
||||
if (addText)
|
||||
{
|
||||
res->setTextFormat(Wt::TextFormat::Plain);
|
||||
res->setText(Wt::WString::fromUTF8(artist->getName()));
|
||||
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Wt::WLink
|
||||
LmsApplication::createReleaseLink(Database::Release::pointer release)
|
||||
{
|
||||
if (const auto mbid {release->getMBID()})
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()}};
|
||||
else
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/release/" + release->getId().toString()};
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WAnchor>
|
||||
LmsApplication::createReleaseAnchor(Database::Release::pointer release, bool addText)
|
||||
{
|
||||
auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release));
|
||||
|
||||
if (addText)
|
||||
{
|
||||
res->setTextFormat(Wt::TextFormat::Plain);
|
||||
res->setText(Wt::WString::fromUTF8(release->getName()));
|
||||
res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WAnchor>
|
||||
LmsApplication::createTrackListAnchor(Database::TrackList::pointer trackList, bool addText)
|
||||
{
|
||||
Wt::WLink link {Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString()};
|
||||
auto res {std::make_unique<Wt::WAnchor>(link)};
|
||||
|
||||
if (addText)
|
||||
{
|
||||
const Wt::WString name {Wt::WString::fromUTF8(std::string {trackList->getName()})};
|
||||
res->setTextFormat(Wt::TextFormat::Plain);
|
||||
res->setText(name);
|
||||
res->setToolTip(name, Wt::TextFormat::Plain);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
LmsApplication::handleException(LmsApplicationException& e)
|
||||
{
|
||||
|
||||
@@ -31,12 +31,8 @@
|
||||
|
||||
namespace Database
|
||||
{
|
||||
class Artist;
|
||||
class Cluster;
|
||||
class Db;
|
||||
class Release;
|
||||
class Session;
|
||||
class TrackList;
|
||||
class User;
|
||||
}
|
||||
|
||||
@@ -80,12 +76,6 @@ class LmsApplication : public Wt::WApplication
|
||||
// Used to classify the message sent to the user
|
||||
void notifyMsg(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {5000});
|
||||
|
||||
static Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
|
||||
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool addText = true);
|
||||
static Wt::WLink createReleaseLink(Database::ObjectPtr<Database::Release> release);
|
||||
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::ObjectPtr<Database::Release> release, bool addText = true);
|
||||
static std::unique_ptr<Wt::WAnchor> createTrackListAnchor(Database::ObjectPtr<Database::TrackList> trackList, bool addText = true);
|
||||
|
||||
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
|
||||
PlayQueue& getPlayQueue() const { return *_playQueue; }
|
||||
ModalManager& getModalManager() const { return *_modalManager; }
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "utils/Utils.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
@@ -155,7 +156,7 @@ replayGainPreAmpGainFromString(const std::string& str)
|
||||
if (!value)
|
||||
return std::nullopt;
|
||||
|
||||
return Utils::clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
|
||||
return ::Utils::clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
|
||||
}
|
||||
|
||||
static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings)
|
||||
@@ -281,7 +282,7 @@ MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
|
||||
{
|
||||
_artist->setTextFormat(Wt::TextFormat::Plain);
|
||||
_artist->setText(Wt::WString::fromUTF8(artists.front()->getName()));
|
||||
_artist->setLink(LmsApp->createArtistLink(artists.front()));
|
||||
_artist->setLink(Utils::createArtistLink(artists.front()));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -293,7 +294,7 @@ MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
|
||||
{
|
||||
_release->setTextFormat(Wt::TextFormat::Plain);
|
||||
_release->setText(Wt::WString::fromUTF8(track->getRelease()->getName()));
|
||||
_release->setLink(LmsApp->createReleaseLink(track->getRelease()));
|
||||
_release->setLink(Utils::createReleaseLink(track->getRelease()));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -500,9 +500,9 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
||||
if (release)
|
||||
{
|
||||
entry->setCondition("if-has-release", true);
|
||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(release));
|
||||
entry->bindWidget("release", Utils::createReleaseAnchor(release));
|
||||
{
|
||||
Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))};
|
||||
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
|
||||
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Small)};
|
||||
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
|
||||
anchor->setImage(std::move(cover));
|
||||
|
||||
@@ -22,10 +22,17 @@
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include <Wt/WAnchor.h>
|
||||
#include <Wt/WText.h>
|
||||
|
||||
#include "services/database/Artist.hpp"
|
||||
#include "services/database/Cluster.hpp"
|
||||
#include "services/database/Release.hpp"
|
||||
#include "services/database/ScanSettings.hpp"
|
||||
#include "services/database/Session.hpp"
|
||||
#include "services/database/Track.hpp"
|
||||
#include "services/database/TrackList.hpp"
|
||||
#include "explore/Filters.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
namespace UserInterface::Utils
|
||||
@@ -112,4 +119,123 @@ namespace UserInterface::Utils
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WContainerWidget>
|
||||
createArtistContainer(const std::vector<Database::ArtistId>& artistIds)
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {std::make_unique<Wt::WContainerWidget>()};
|
||||
|
||||
bool firstArtist {true};
|
||||
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
for (const ArtistId artistId : artistIds)
|
||||
{
|
||||
const Artist::pointer artist {Artist::find(LmsApp->getDbSession(), artistId)};
|
||||
if (!artist)
|
||||
continue;
|
||||
|
||||
if (!firstArtist)
|
||||
artistContainer->addNew<Wt::WText>(" · ");
|
||||
|
||||
auto anchor {createArtistAnchor(artist)};
|
||||
anchor->addStyleClass("link-success text-decoration-none"); // hack
|
||||
artistContainer->addWidget(std::move(anchor));
|
||||
firstArtist = false;
|
||||
}
|
||||
|
||||
return artistContainer;
|
||||
}
|
||||
|
||||
Wt::WLink
|
||||
createArtistLink(Database::Artist::pointer artist)
|
||||
{
|
||||
if (const auto mbid {artist->getMBID()})
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()}};
|
||||
else
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString()};
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WAnchor>
|
||||
createArtistAnchor(Database::Artist::pointer artist, bool setText)
|
||||
{
|
||||
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
|
||||
|
||||
if (setText)
|
||||
{
|
||||
res->setTextFormat(Wt::TextFormat::Plain);
|
||||
res->setText(Wt::WString::fromUTF8(artist->getName()));
|
||||
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Wt::WLink
|
||||
createReleaseLink(Database::Release::pointer release)
|
||||
{
|
||||
if (const auto mbid {release->getMBID()})
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()}};
|
||||
else
|
||||
return Wt::WLink {Wt::LinkType::InternalPath, "/release/" + release->getId().toString()};
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WAnchor>
|
||||
createReleaseAnchor(Database::Release::pointer release, bool setText)
|
||||
{
|
||||
auto res = std::make_unique<Wt::WAnchor>(createReleaseLink(release));
|
||||
|
||||
if (setText)
|
||||
{
|
||||
res->setTextFormat(Wt::TextFormat::Plain);
|
||||
res->setText(Wt::WString::fromUTF8(release->getName()));
|
||||
res->setToolTip(Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WAnchor>
|
||||
createTrackListAnchor(Database::TrackList::pointer trackList, bool setText)
|
||||
{
|
||||
Wt::WLink link {Wt::LinkType::InternalPath, "/tracklist/" + trackList->getId().toString()};
|
||||
auto res {std::make_unique<Wt::WAnchor>(link)};
|
||||
|
||||
if (setText)
|
||||
{
|
||||
const Wt::WString name {Wt::WString::fromUTF8(std::string {trackList->getName()})};
|
||||
res->setTextFormat(Wt::TextFormat::Plain);
|
||||
res->setText(name);
|
||||
res->setToolTip(name, Wt::TextFormat::Plain);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WContainerWidget>
|
||||
createClustersForTrack(Database::Track::pointer track, Filters& filters)
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
std::unique_ptr<Wt::WContainerWidget> clusterContainer {std::make_unique<Wt::WContainerWidget>()};
|
||||
|
||||
const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
|
||||
const auto clusterGroups {track->getClusterGroups(clusterTypes, 3)};
|
||||
|
||||
for (const auto& clusters : clusterGroups)
|
||||
{
|
||||
for (const Cluster::pointer& cluster : clusters)
|
||||
{
|
||||
const ClusterId clusterId {cluster->getId()};
|
||||
Wt::WInteractWidget* entry {clusterContainer->addWidget(Utils::createCluster(clusterId))};
|
||||
entry->clicked().connect([&filters, clusterId]
|
||||
{
|
||||
filters.add(clusterId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return clusterContainer;
|
||||
}
|
||||
}
|
||||
|
||||
+28
-2
@@ -22,22 +22,48 @@
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
#include <Wt/WContainerWidget.h>
|
||||
#include <Wt/WImage.h>
|
||||
#include <Wt/WInteractWidget.h>
|
||||
|
||||
#include "services/database/ArtistId.hpp"
|
||||
#include "services/database/ClusterId.hpp"
|
||||
#include "services/database/Object.hpp"
|
||||
#include "services/database/ReleaseId.hpp"
|
||||
#include "services/database/TrackId.hpp"
|
||||
|
||||
#include "resource/CoverResource.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
class Artist;
|
||||
class Cluster;
|
||||
class Release;
|
||||
class Track;
|
||||
class TrackList;
|
||||
}
|
||||
|
||||
namespace UserInterface
|
||||
{
|
||||
class Filters;
|
||||
}
|
||||
|
||||
namespace UserInterface::Utils
|
||||
{
|
||||
std::string
|
||||
durationToString(std::chrono::milliseconds msDuration);
|
||||
std::string durationToString(std::chrono::milliseconds msDuration);
|
||||
|
||||
std::unique_ptr<Wt::WImage> createCover(Database::ReleaseId releaseId, CoverResource::Size size);
|
||||
std::unique_ptr<Wt::WImage> createCover(Database::TrackId trackId, CoverResource::Size size);
|
||||
|
||||
std::unique_ptr<Wt::WInteractWidget> createCluster(Database::ClusterId clusterId, bool canDelete = false);
|
||||
|
||||
std::unique_ptr<Wt::WContainerWidget> createArtistContainer(const std::vector<Database::ArtistId>& artists);
|
||||
|
||||
Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist);
|
||||
std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool setText = true);
|
||||
Wt::WLink createReleaseLink(Database::ObjectPtr<Database::Release> release);
|
||||
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::ObjectPtr<Database::Release> release, bool setText = true);
|
||||
std::unique_ptr<Wt::WAnchor> createTrackListAnchor(Database::ObjectPtr<Database::TrackList> trackList, bool setText = true);
|
||||
|
||||
std::unique_ptr<Wt::WContainerWidget> createClustersForTrack(Database::ObjectPtr<Database::Track> track, Filters& filters);
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
static inline constexpr Field UpdatePeriodField {"update-period"};
|
||||
static inline constexpr Field UpdateStartTimeField {"update-start-time"};
|
||||
static inline constexpr Field RecommendationEngineTypeField {"recommendation-engine-type"};
|
||||
static inline constexpr Field TagsField {"tags"};
|
||||
static inline constexpr Field ClustersField {"clusters"};
|
||||
|
||||
using UpdatePeriodModel = ValueStringModel<ScanSettings::UpdatePeriod>;
|
||||
|
||||
@@ -65,7 +65,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
addField(UpdatePeriodField);
|
||||
addField(UpdateStartTimeField);
|
||||
addField(RecommendationEngineTypeField);
|
||||
addField(TagsField);
|
||||
addField(ClustersField);
|
||||
|
||||
auto dirValidator {createDirectoryValidator()};
|
||||
dirValidator->setMandatory(true);
|
||||
@@ -114,7 +114,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
{
|
||||
std::vector<std::string> names;
|
||||
std::transform(clusterTypes.begin(), clusterTypes.end(), std::back_inserter(names), [](auto clusterType) { return clusterType->getName(); });
|
||||
setValue(TagsField, StringUtils::joinStrings(names, " "));
|
||||
setValue(ClustersField, StringUtils::joinStrings(names, " "));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +138,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
|
||||
if (recommendationEngineTypeRow)
|
||||
scanSettings.modify()->setRecommendationEngineType(_recommendationEngineTypeModel->getValue(*recommendationEngineTypeRow));
|
||||
|
||||
auto clusterTypes {StringUtils::splitStringCopy(valueText(TagsField).toUTF8(), " ")};
|
||||
auto clusterTypes {StringUtils::splitStringCopy(valueText(ClustersField).toUTF8(), " ")};
|
||||
scanSettings.modify()->setClusterTypes(LmsApp->getDbSession(), std::set<std::string>(clusterTypes.begin(), clusterTypes.end()));
|
||||
}
|
||||
|
||||
@@ -215,8 +215,8 @@ DatabaseSettingsView::refreshView()
|
||||
recommendationEngineType->setModel(model->recommendationEngineTypeModel());
|
||||
t->setFormWidget(DatabaseSettingsModel::RecommendationEngineTypeField, std::move(recommendationEngineType));
|
||||
|
||||
// Tags
|
||||
t->setFormWidget(DatabaseSettingsModel::TagsField, std::make_unique<Wt::WLineEdit>());
|
||||
// Clusters
|
||||
t->setFormWidget(DatabaseSettingsModel::ClustersField, std::make_unique<Wt::WLineEdit>());
|
||||
|
||||
// Buttons
|
||||
Wt::WPushButton *saveBtn = t->bindWidget("apply-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.apply")));
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
#include <Wt/WAnchor.h>
|
||||
|
||||
#include "services/database/Artist.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
namespace UserInterface::ArtistListHelpers
|
||||
{
|
||||
@@ -29,7 +29,7 @@ namespace UserInterface::ArtistListHelpers
|
||||
createEntry(const Database::ObjectPtr<Database::Artist>& artist)
|
||||
{
|
||||
auto res {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry"))};
|
||||
res->bindWidget("name", LmsApplication::createArtistAnchor(artist));
|
||||
res->bindWidget("name", Utils::createArtistAnchor(artist));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -335,7 +335,7 @@ Artist::addSomeNonReleaseTracks()
|
||||
}
|
||||
|
||||
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
|
||||
_trackContainer->add(TrackListHelpers::createEntry(track, _playQueueController));
|
||||
_trackContainer->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||
|
||||
areTracksAdded = true;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ Artists::refreshArtistLinkTypes()
|
||||
if (!usedLinkTypes.contains(linkType))
|
||||
return;
|
||||
|
||||
linkTypeModel->add(Wt::WString::tr(std::string {stringKey}), linkType);
|
||||
linkTypeModel->add(Wt::WString::trn(std::string {stringKey}, 2), linkType);
|
||||
}};
|
||||
|
||||
linkTypeModel->clear();
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "services/database/Artist.hpp"
|
||||
#include "services/database/Release.hpp"
|
||||
|
||||
#include "LmsApplication.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
using namespace Database;
|
||||
@@ -39,11 +38,11 @@ namespace UserInterface::ReleaseListHelpers
|
||||
{
|
||||
auto entry {std::make_unique<Wt::WTemplate>(Wt::WString::tr(templateKey))};
|
||||
|
||||
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
|
||||
entry->bindWidget("release-name", Utils::createReleaseAnchor(release));
|
||||
entry->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
|
||||
{
|
||||
Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))};
|
||||
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
|
||||
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Large)};
|
||||
cover->addStyleClass("Lms-cover-release Lms-cover-anchor");
|
||||
anchor->setImage(std::move(cover));
|
||||
@@ -62,7 +61,7 @@ namespace UserInterface::ReleaseListHelpers
|
||||
else if (artists.size() == 1 && !isSameArtist)
|
||||
{
|
||||
entry->setCondition("if-has-artist", true);
|
||||
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
|
||||
entry->bindWidget("artist-name", Utils::createArtistAnchor(artists.front()));
|
||||
}
|
||||
|
||||
if (showYear)
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "explore/Filters.hpp"
|
||||
#include "explore/PlayQueueController.hpp"
|
||||
#include "explore/ReleaseListHelpers.hpp"
|
||||
#include "explore/TrackListHelpers.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
#include "LmsApplicationException.hpp"
|
||||
#include "MediaPlayer.hpp"
|
||||
@@ -270,7 +271,7 @@ Release::refreshView()
|
||||
{
|
||||
if (!firstArtist)
|
||||
artistsContainer->addNew<Wt::WText>(" · ");
|
||||
auto anchor {LmsApplication::createArtistAnchor(artist)};
|
||||
auto anchor {Utils::createArtistAnchor(artist)};
|
||||
anchor->addStyleClass("link-success text-decoration-none"); // hack
|
||||
artistsContainer->addWidget(std::move(anchor));
|
||||
firstArtist = false;
|
||||
@@ -319,6 +320,9 @@ Release::refreshView()
|
||||
|
||||
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||
->setLink(Wt::WLink {std::make_unique<DownloadTrackResource>(trackId)});
|
||||
|
||||
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
|
||||
->clicked().connect([=] { TrackListHelpers::showTrackInfoModal(trackId, _filters); });
|
||||
}
|
||||
|
||||
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||
@@ -361,7 +365,7 @@ Release::refreshReleaseArtists(const Database::Release::pointer& release)
|
||||
for (const auto& artist : artists)
|
||||
{
|
||||
Wt::WTemplate* artistTemplate {artistsContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-release-artist"))};
|
||||
artistTemplate->bindWidget("artist", LmsApplication::createArtistAnchor(artist));
|
||||
artistTemplate->bindWidget("artist", Utils::createArtistAnchor(artist));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace UserInterface
|
||||
SearchView::SearchView(Filters& filters, PlayQueueController& playQueueController)
|
||||
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Search.template")}
|
||||
, _playQueueController {playQueueController}
|
||||
, _filters {filters}
|
||||
, _artistCollector {filters, ArtistCollector::Mode::Search, getMaxCount(Mode::Artist)}
|
||||
, _releaseCollector {filters, ReleaseCollector::Mode::Search, getMaxCount(Mode::Release)}
|
||||
, _trackCollector {filters, TrackCollector::Mode::Search, getMaxCount(Mode::Track)}
|
||||
@@ -152,7 +153,7 @@ namespace UserInterface
|
||||
for (const TrackId trackId : trackIds.results)
|
||||
{
|
||||
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
|
||||
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController));
|
||||
_tracks->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,7 @@ namespace UserInterface
|
||||
void addSomeTracks();
|
||||
|
||||
PlayQueueController& _playQueueController;
|
||||
Filters& _filters;
|
||||
ArtistCollector _artistCollector;
|
||||
ReleaseCollector _releaseCollector;
|
||||
TrackCollector _trackCollector;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <Wt/WImage.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
|
||||
#include "av/IAudioFile.hpp"
|
||||
#include "services/database/Artist.hpp"
|
||||
#include "services/database/Release.hpp"
|
||||
#include "services/scrobbling/IScrobblingService.hpp"
|
||||
@@ -36,29 +37,96 @@
|
||||
#include "resource/CoverResource.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
#include "MediaPlayer.hpp"
|
||||
#include "ModalManager.hpp"
|
||||
#include "Utils.hpp"
|
||||
|
||||
using namespace Database;
|
||||
|
||||
namespace UserInterface::TrackListHelpers
|
||||
{
|
||||
void
|
||||
showTrackInfoModal(Database::TrackId trackId, Filters& filters)
|
||||
{
|
||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
||||
|
||||
const Database::Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
|
||||
if (!track)
|
||||
return;
|
||||
|
||||
auto trackInfo {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Tracks.template.track-info"))};
|
||||
Wt::WWidget* trackInfoPtr {trackInfo.get()};
|
||||
trackInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
|
||||
Wt::WContainerWidget* artistTable {trackInfo->bindNew<Wt::WContainerWidget>("artist-table")};
|
||||
|
||||
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
|
||||
{
|
||||
Artist::FindParameters params;
|
||||
params.setTrack(trackId);
|
||||
params.setLinkType(linkType);
|
||||
const auto artistIds {Artist::find(LmsApp->getDbSession(), params)};
|
||||
if (artistIds.results.empty())
|
||||
return;
|
||||
|
||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(artistIds.results)};
|
||||
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Tracks.template.track-info.artists"))};
|
||||
artistsEntry->bindString("type", Wt::WString::trn(type, artistContainer->count()));
|
||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
||||
artistTable->addWidget(std::move(artistsEntry));
|
||||
};
|
||||
|
||||
addArtists(TrackArtistLinkType::Composer, "Lms.Explore.Artists.linktype-composer");
|
||||
addArtists(TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.linktype-lyricist");
|
||||
addArtists(TrackArtistLinkType::Mixer, "Lms.Explore.Artists.linktype-mixer");
|
||||
addArtists(TrackArtistLinkType::Remixer, "Lms.Explore.Artists.linktype-remixer");
|
||||
addArtists(TrackArtistLinkType::Producer, "Lms.Explore.Artists.linktype-producer");
|
||||
|
||||
if (const auto audioFile {Av::parseAudioFile(track->getPath())})
|
||||
{
|
||||
const std::optional<Av::StreamInfo> audioStream {audioFile->getBestStreamInfo()};
|
||||
if (audioStream)
|
||||
{
|
||||
trackInfo->setCondition("if-has-codec", true);
|
||||
trackInfo->bindString("codec", audioStream->codec);
|
||||
if (audioStream->bitrate)
|
||||
{
|
||||
trackInfo->setCondition("if-has-bitrate", true);
|
||||
trackInfo->bindString("bitrate", std::to_string(audioStream->bitrate / 1000) + " kbps");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trackInfo->bindString("duration", Utils::durationToString(track->getDuration()));
|
||||
|
||||
Wt::WContainerWidget* clusterContainer {trackInfo->bindWidget("clusters", Utils::createClustersForTrack(track, filters))};
|
||||
if (clusterContainer->count() > 0)
|
||||
trackInfo->setCondition("if-has-clusters", true);
|
||||
|
||||
Wt::WPushButton* okBtn {trackInfo->bindNew<Wt::WPushButton>("ok-btn", Wt::WString::tr("Lms.ok"))};
|
||||
okBtn->clicked().connect([=]
|
||||
{
|
||||
LmsApp->getModalManager().dispose(trackInfoPtr);
|
||||
});
|
||||
|
||||
LmsApp->getModalManager().show(std::move(trackInfo));
|
||||
}
|
||||
|
||||
std::unique_ptr<Wt::WWidget>
|
||||
createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController)
|
||||
createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController, Filters& filters)
|
||||
{
|
||||
auto entry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"))};
|
||||
auto* entryPtr {entry.get()};
|
||||
|
||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||
|
||||
const auto artists {track->getArtists({TrackArtistLinkType::Artist})};
|
||||
const Release::pointer release {track->getRelease()};
|
||||
const TrackId trackId {track->getId()};
|
||||
|
||||
if (track->getRelease())
|
||||
{
|
||||
entry->setCondition("if-has-release", true);
|
||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
||||
Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))};
|
||||
entry->bindWidget("release", Utils::createReleaseAnchor(track->getRelease()));
|
||||
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
|
||||
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Small)};
|
||||
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
|
||||
anchor->setImage(std::move((cover)));
|
||||
@@ -109,6 +177,9 @@ namespace UserInterface::TrackListHelpers
|
||||
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||
->setLink(Wt::WLink {std::make_unique<DownloadTrackResource>(trackId)});
|
||||
|
||||
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
|
||||
->clicked().connect([trackId, &filters] { showTrackInfoModal(trackId, filters); });
|
||||
|
||||
LmsApp->getMediaPlayer().trackLoaded.connect(entryPtr, [=] (Database::TrackId loadedTrackId)
|
||||
{
|
||||
entryPtr->toggleStyleClass("Lms-entry-playing", loadedTrackId == trackId);
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
|
||||
#include <Wt/WWidget.h>
|
||||
#include "services/database/Object.hpp"
|
||||
#include "services/database/TrackId.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
@@ -32,10 +33,12 @@ namespace Database
|
||||
namespace UserInterface
|
||||
{
|
||||
class PlayQueueController;
|
||||
class Filters;
|
||||
}
|
||||
|
||||
namespace UserInterface::TrackListHelpers
|
||||
{
|
||||
std::unique_ptr<Wt::WWidget> createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController);
|
||||
void showTrackInfoModal(Database::TrackId trackId, Filters& filters);
|
||||
std::unique_ptr<Wt::WWidget> createEntry(const Database::ObjectPtr<Database::Track>& track, PlayQueueController& playQueueController, Filters& filters);
|
||||
} // namespace UserInterface
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ namespace UserInterface
|
||||
for (const TrackId trackId : trackIds.results)
|
||||
{
|
||||
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
|
||||
_container->add(TrackListHelpers::createEntry(track, _playQueueController));
|
||||
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||
}
|
||||
|
||||
_container->setHasMore(trackIds.moreResults && _container->getCount() < _maxCount);
|
||||
|
||||
@@ -136,7 +136,7 @@ namespace UserInterface
|
||||
const TrackListId trackListId {trackList->getId()};
|
||||
|
||||
WTemplate* entry {_container->addNew<Template>(Wt::WString::tr("Lms.Explore.TrackLists.template.entry"))};
|
||||
entry->bindWidget("name", LmsApplication::createTrackListAnchor(trackList));
|
||||
entry->bindWidget("name", Utils::createTrackListAnchor(trackList));
|
||||
|
||||
assert(_trackListWidgets.find(trackListId) == std::cend(_trackListWidgets));
|
||||
_trackListWidgets.emplace(trackListId, entry);
|
||||
|
||||
@@ -37,6 +37,7 @@ namespace UserInterface {
|
||||
|
||||
Tracks::Tracks(Filters& filters, PlayQueueController& playQueueController)
|
||||
: Template {Wt::WString::tr("Lms.Explore.Tracks.template")}
|
||||
, _filters {filters}
|
||||
, _playQueueController {playQueueController}
|
||||
, _trackCollector {filters, _defaultMode, _maxCount}
|
||||
{
|
||||
@@ -124,7 +125,7 @@ Tracks::addSome()
|
||||
for (const TrackId trackId : trackIds.results)
|
||||
{
|
||||
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
|
||||
_container->add(TrackListHelpers::createEntry(track, _playQueueController));
|
||||
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||
}
|
||||
|
||||
_container->setHasMore(trackIds.moreResults);
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace UserInterface
|
||||
static constexpr std::size_t _batchSize {6};
|
||||
static constexpr std::size_t _maxCount {8000};
|
||||
|
||||
Filters& _filters;
|
||||
PlayQueueController& _playQueueController;
|
||||
Wt::WWidget* _currentActiveItem {};
|
||||
InfiniteScrollingContainer* _container {};
|
||||
|
||||
Reference in New Issue
Block a user