Merge branch 'new-track-layout-v2' into develop, fix #313

This commit is contained in:
emeric
2023-04-02 15:14:33 +02:00
18 changed files with 179 additions and 62 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<message id="Lms.Admin.Users.template">
<h3>${tr:Lms.Admin.Users.users}</h3>
${users class="d-grid gap-1 mb-3"}
${users class="d-grid gap-1 mb-3 Lms-row-container"}
${<if-can-create-user>}
${add-btn class="btn btn-primary"}
${</if-can-create-user>}
+6 -2
View File
@@ -27,10 +27,14 @@
</div>
<div class="flex-fill text-center text-truncate">
${title class="d-block text-truncate"}
${release class="text-decoration-none text-truncate link-success"}
${artist class="d-inline text-decoration-none text-truncate link-success"}
<span class="d-none d-md-inline">
${separator}
${release class="text-decoration-none text-truncate link-success"}
</span>
</div>
<div class="p-2 d-none d-sm-block text-muted text-nowrap">
<span id="lms-transcoding-active" title="${tr:Lms.Player.transcoding-active}" class="p-2" style="visibility:hidden"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></span>
<span id="lms-transcoding-active" title="${tr:Lms.Player.transcoding-active}" class="p-2" style="display:none"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></span>
<span id="lms-mp-curtime">--</span> / <span id="lms-mp-duration">--</span>
</div>
<div class="p-2 d-none d-md-flex align-items-center Lms-player-volume-container">
+25 -9
View File
@@ -30,28 +30,44 @@
${entries}
</message>
<message id="Lms.PlayQueue.template.entry-container">
${elements class="Lms-row-container"}
${loading-indicator}
</message>
<message id="Lms.PlayQueue.template.entry">
<div class="d-flex align-items-center rounded Lms-bg-dark-hover mb-2">
<div class="p-1 Lms-cover-small">
<div class="d-flex align-items-center mb-2">
<div class="p-1">
${cover}
</div>
<div class="p-2 flex-fill text-truncate">
<div class="d-block text-truncate">${name}</div>
${<if-has-release>}${release class="text-decoration-none link-success text-truncate"}${</if-has-release>}
<div class="row align-items-center flex-fill overflow-hidden">
<div class="col-12 col-md-6 col-lg-4">
<div class="p-2 overflow-hidden">
<div class="d-block text-truncate">${name}</div>
${<if-has-artists>}${artists class="d-block d-md-none text-truncate"}${</if-has-artists>}
</div>
</div>
<div class="col-md-6 col-lg-4">
${<if-has-artists>}${artists-md class="d-none d-md-block text-truncate"}${</if-has-artists>}
</div>
<div class="col-lg-4 text-truncate">
${<if-has-release>}${release class="d-none d-lg-inline text-truncate text-decoration-none link-success"}${</if-has-release>}
</div>
</div>
<div class="p-2 d-none d-sm-block text-muted">
<div class="p-2 d-none d-sm-block text-muted text-center Lms-tracks-entry-duration">
${duration}
</div>
<div class="p-2 d-flex">
${play-btn class="btn btn-sm btn-outline-secondary border-0"}
<div class="dropdown d-none d-sm-block">
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
${del-btn class="btn btn-sm btn-outline-secondary border-0"}
<div class="dropdown d-inline-block">
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
<li>${play class="dropdown-item"}</li>
<li>${star class="dropdown-item"}</li>
<li>${download class="dropdown-item"}</li>
</ul>
</div>
${del-btn class="btn btn-sm btn-outline-secondary border-0"}
</div>
</div>
</message>
+17 -9
View File
@@ -51,30 +51,38 @@
<message id="Lms.Explore.Release.template.entry-disc">
<h4>${disc-title}</h4>
${tracks class="d-grid gap-1"}
${tracks class="d-grid gap-1 Lms-row-container"}
</message>
<message id="Lms.Explore.Release.template.entry-nodisc">
${tracks class="d-grid gap-1"}
${tracks class="d-grid gap-1 Lms-row-container"}
</message>
<message id="Lms.Explore.Release.template.entry">
<div class="d-flex align-items-center rounded Lms-bg-dark-hover">
<div class="p-2 text-muted text-end" style="min-width: 2.1rem;">
<div class="d-flex align-items-center">
<div class="p-2 text-muted text-end Lms-tracks-entry-position">
${<if-has-track-number>}${track-number}${</if-has-track-number>}
</div>
<div class="p-2 flex-fill">
${name}
${<if-has-artists>}${artists}${</if-has-artists>}
<div class="row align-items-center flex-fill overflow-hidden">
<div class="col-12 col-md-6">
<div class="p-2">
${name}
${<if-has-artists>}${artists class="d-block d-md-none"}${</if-has-artists>}
</div>
</div>
<div class="col-md-6">
${<if-has-artists>}${artists-md class="d-none d-md-block"}${</if-has-artists>}
</div>
</div>
<div class="p-2 d-none d-sm-block text-muted">
<div class="p-2 d-none d-sm-block text-muted text-center Lms-tracks-entry-duration">
${duration}
</div>
<div class="p-2 d-flex">
${play-btn class="btn btn-sm btn-outline-secondary border-0"}
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
<div class="dropdown d-inline-block">
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
<li>${play class="dropdown-item"}</li>
<li>${play-last class="dropdown-item"}</li>
<li>${star class="dropdown-item"}</li>
<li>${download class="dropdown-item"}</li>
+5
View File
@@ -21,6 +21,11 @@
${tracks}
</message>
<message id="Lms.Explore.TrackList.template.entry-container">
${elements class="Lms-row-container"}
${loading-indicator}
</message>
<message id="Lms.Explore.TrackList.template.delete-tracklist">
<div class="modal fade" tabindex="-1">
<div class="modal-dialog">
+23 -11
View File
@@ -37,23 +37,39 @@
${tracks}
</message>
<message id="Lms.Explore.Tracks.template.entry-container">
${elements class="Lms-row-container"}
${loading-indicator}
</message>
<message id="Lms.Explore.Tracks.template.entry">
<div class="d-flex align-items-center rounded Lms-bg-dark-hover mb-2">
<div class="p-1 Lms-cover-small">
<div class="d-flex align-items-center mb-2">
<div class="p-1">
${cover}
</div>
<div class="p-2 flex-fill text-truncate">
<div class="d-block text-truncate">${name}</div>
${<if-has-release>}${release class="text-decoration-none link-success text-truncate"}${</if-has-release>}
<div class="row align-items-center flex-fill overflow-hidden">
<div class="col-12 col-md-6 col-lg-4">
<div class="p-2 overflow-hidden">
<div class="d-block text-truncate">${name}</div>
${<if-has-artists>}${artists class="d-block d-md-none text-truncate"}${</if-has-artists>}
</div>
</div>
<div class="col-md-6 col-lg-4">
${<if-has-artists>}${artists-md class="d-none d-md-block text-truncate"}${</if-has-artists>}
</div>
<div class="col-lg-4 text-truncate">
${<if-has-release>}${release class="d-none d-lg-inline text-truncate text-decoration-none link-success"}${</if-has-release>}
</div>
</div>
<div class="p-2 d-none d-sm-block text-muted">
<div class="p-2 d-none d-sm-block text-muted text-center Lms-tracks-entry-duration">
${duration}
</div>
<div class="p-2 d-flex">
${play-btn class="btn btn-sm btn-outline-secondary border-0"}
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
<div class="dropdown d-inline-block">
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
<li>${play class="dropdown-item"}</li>
<li>${play-last class="dropdown-item"}</li>
<li>${star class="dropdown-item"}</li>
<li>${download class="dropdown-item"}</li>
@@ -64,10 +80,6 @@
</div>
</message>
<message id="Lms.Explore.Tracks.template.entry-artist">
${artist class="text-decoration-none link-secondary"}
</message>
<message id="Lms.Explore.Tracks.template.track-info">
<div class="modal fade" tabindex="-1">
<div class="modal-dialog modal-dialog-scrollable">
+26 -1
View File
@@ -1,5 +1,9 @@
/* Generic rules */
:root {
--lms-striped-bg: #022f3a;
}
.link-success {
transition: color 0.15s ease;
}
@@ -16,6 +20,19 @@
background-color: var(--bs-dark);
}
.Lms-row-container {
--lms-striped-bg: #022f3a;
--lms-hover-bg: var(--bs-dark);
}
.Lms-row-container > :nth-child(odd) {
background-color: var(--lms-striped-bg);
}
.Lms-row-container > *:hover {
background-color: var(--lms-hover-bg);
}
/* Specific rules */
body {
padding-bottom: 78px;
@@ -32,7 +49,7 @@ body {
}
.Lms-entry-playing {
background-color: var(--bs-dark);
background-color: #0d4655!important;
}
.Lms-cover {
@@ -144,3 +161,11 @@ body {
display: block;
padding-bottom: 100%;
}
.Lms-tracks-entry-position{
min-width: 2.1rem;
}
.Lms-tracks-entry-duration {
min-width: 4rem;
}
+2 -2
View File
@@ -271,10 +271,10 @@ LMS.mediaplayer = function () {
_elems.audio.addEventListener("canplay", function() {
if (_getAudioMode() == Mode.Transcode) {
_elems.transcodingActive.style.visibility = "visible";
_elems.transcodingActive.style.display = "inline";
}
else {
_elems.transcodingActive.style.visibility = "hidden";
_elems.transcodingActive.style.display = "none";
}
});
-1
View File
@@ -248,7 +248,6 @@ getMedium(const TagMap& tags)
if (strings.size() == 2)
medium->trackCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
// Expecting 'Number[/Total]'
medium->position = getPropertyValueAs<std::size_t>(tags, "DISCNUMBER");
+8 -10
View File
@@ -401,9 +401,8 @@ Track::getArtists(EnumSet<TrackArtistLinkType> linkTypes) const
oss << " AND t_a_l.type IN (";
bool first {true};
for (TrackArtistLinkType type : linkTypes)
for ([[maybe_unused]] TrackArtistLinkType type : linkTypes)
{
(void) type;
if (!first)
oss << ", ";
oss << "?";
@@ -430,7 +429,7 @@ Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
std::ostringstream oss;
oss <<
"SELECT a.id from artist a"
"SELECT DISTINCT a.id from artist a"
" INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id"
" INNER JOIN track t ON t.id = t_a_l.track_id";
@@ -439,9 +438,8 @@ Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
oss << " AND t_a_l.type IN (";
bool first {true};
for (TrackArtistLinkType type : linkTypes)
for ([[maybe_unused]] TrackArtistLinkType type : linkTypes)
{
(void) type;
if (!first)
oss << ", ";
oss << "?";
@@ -450,14 +448,14 @@ Track::getArtistIds(EnumSet<TrackArtistLinkType> linkTypes) const
oss << ")";
}
Wt::Dbo::Query<ArtistId> query {session()->query<ArtistId>(oss.str())
.where("t.id = ?").bind(getId())};
auto query {session()->query<ArtistId>(oss.str())};
for (TrackArtistLinkType type : linkTypes)
query.bind(type);
Wt::Dbo::collection<ArtistId> res = query;
return std::vector<ArtistId>(std::begin(res), std::end(res));
query.where("t.id = ?").bind(getId());
auto res {query.resultList()};
return std::vector<ArtistId>(std::cbegin(res), std::cend(res));
}
std::vector<TrackArtistLink::pointer>
@@ -91,6 +91,18 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
EXPECT_EQ(track->getArtists({}).size(), 1);
}
{
auto transaction {session.createSharedTransaction()};
auto artists {track->getArtistIds({TrackArtistLinkType::Artist})};
ASSERT_EQ(artists.size(), 1);
EXPECT_EQ(artists.front(), artist.getId());
ASSERT_EQ(track->getArtistIds({TrackArtistLinkType::Artist}).size(), 1);
EXPECT_TRUE(track->getArtistIds({TrackArtistLinkType::ReleaseArtist}).empty());
EXPECT_EQ(track->getArtistIds({}).size(), 1);
}
{
auto transaction {session.createSharedTransaction()};
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist"))};
+10
View File
@@ -210,6 +210,7 @@ MediaPlayer::MediaPlayer()
_title = bindNew<Wt::WText>("title");
_artist = bindNew<Wt::WAnchor>("artist");
_release = bindNew<Wt::WAnchor>("release");
_separator = bindNew<Wt::WText>("separator");
_playQueue = bindNew<Wt::WPushButton>("playqueue-btn", Wt::WString::tr("Lms.MediaPlayer.template.playqueue-btn").arg(0), Wt::TextFormat::XHTML);
_playQueue->setLink(Wt::WLink {Wt::LinkType::InternalPath, "/playqueue"});
_playQueue->setToolTip(tr("Lms.PlayQueue.playqueue"));
@@ -275,6 +276,8 @@ MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
_title->setTextFormat(Wt::TextFormat::Plain);
_title->setText(Wt::WString::fromUTF8(track->getName()));
bool needSeparator {true};
if (!artists.empty())
{
_artist->setTextFormat(Wt::TextFormat::Plain);
@@ -285,6 +288,7 @@ MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
{
_artist->setText("");
_artist->setLink({});
needSeparator = false;
}
if (track->getRelease())
@@ -297,7 +301,13 @@ MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain)
{
_release->setText("");
_release->setLink({});
needSeparator = false;
}
if (needSeparator)
_separator->setText("");
else
_separator->setText("");
}
LMS_LOG(UI, DEBUG) << "Running js = '" << oss.str() << "'";
+1
View File
@@ -127,6 +127,7 @@ class MediaPlayer : public Wt::WTemplate
Wt::WText* _title {};
Wt::WAnchor* _release {};
Wt::WText* _separator {};
Wt::WAnchor* _artist {};
Wt::WPushButton* _playQueue {};
};
+18 -1
View File
@@ -28,6 +28,7 @@
#include <Wt/WStackedWidget.h>
#include <Wt/WTemplateFormView.h>
#include "services/database/Artist.hpp"
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
@@ -136,7 +137,7 @@ PlayQueue::PlayQueue()
saveAsTrackList();
});
_entriesContainer = bindNew<InfiniteScrollingContainer>("entries");
_entriesContainer = bindNew<InfiniteScrollingContainer>("entries", Wt::WString::tr("Lms.PlayQueue.template.entry-container"));
_entriesContainer->onRequestElements.connect([this]
{
addSome();
@@ -489,6 +490,15 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
const auto artists {track->getArtistIds({Database::TrackArtistLinkType::Artist})};
LMS_LOG(UI, DEBUG) << "Found " << artists.size() << " artists!";
if (!artists.empty())
{
entry->setCondition("if-has-artists", true);
entry->bindWidget("artists", Utils::createArtistContainer(artists));
entry->bindWidget("artists-md", Utils::createArtistContainer(artists));
}
const auto release {track->getRelease()};
if (release)
{
@@ -542,6 +552,13 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
});
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
->clicked().connect([=]
{
const std::optional<std::size_t> pos {_entriesContainer->getIndexOf(*entry)};
if (pos)
loadTrack(*pos, true);
});
auto isStarred {[=] { return Service<Scrobbling::IScrobblingService>::get()->isStarred(LmsApp->getUserId(), trackId); }};
+8 -13
View File
@@ -377,22 +377,12 @@ Release::refreshView()
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
const auto artists {track->getArtists({TrackArtistLinkType::Artist})};
const auto artists {track->getArtistIds({TrackArtistLinkType::Artist})};
if (variousArtists && !artists.empty())
{
entry->setCondition("if-has-artists", true);
Wt::WContainerWidget* artistsContainer {entry->bindNew<Wt::WContainerWidget>("artists")};
bool firstArtist {true};
for (const auto& artist : artists)
{
if (!firstArtist)
artistsContainer->addNew<Wt::WText>(" · ");
auto anchor {Utils::createArtistAnchor(artist)};
anchor->addStyleClass("link-success text-decoration-none"); // hack
artistsContainer->addWidget(std::move(anchor));
firstArtist = false;
}
entry->bindWidget("artists", Utils::createArtistContainer(artists));
entry->bindWidget("artists-md", Utils::createArtistContainer(artists));
}
auto trackNumber {track->getTrackNumber()};
@@ -410,6 +400,11 @@ Release::refreshView()
{
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
->clicked().connect([=]
{
_playQueueController.playTrackInRelease(trackId);
});
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
->clicked().connect([=]
{
+15
View File
@@ -31,6 +31,7 @@
#include "services/database/Track.hpp"
#include "services/database/TrackArtistLink.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "utils/Logger.hpp"
#include "utils/Service.hpp"
#include "common/Template.hpp"
@@ -160,6 +161,15 @@ namespace UserInterface::TrackListHelpers
const Release::pointer release {track->getRelease()};
const TrackId trackId {track->getId()};
const auto artists {track->getArtistIds({TrackArtistLinkType::Artist})};
LMS_LOG(UI, DEBUG) << "Found " << artists.size() << " artists!";
if (!artists.empty())
{
entry->setCondition("if-has-artists", true);
entry->bindWidget("artists", Utils::createArtistContainer(artists));
entry->bindWidget("artists-md", Utils::createArtistContainer(artists));
}
if (track->getRelease())
{
entry->setCondition("if-has-release", true);
@@ -185,6 +195,11 @@ namespace UserInterface::TrackListHelpers
});
entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML);
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
->clicked().connect([trackId, &playQueueController]
{
playQueueController.processCommand(PlayQueueController::Command::Play, {trackId});
});
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
->clicked().connect([=, &playQueueController]
{
+1 -1
View File
@@ -174,7 +174,7 @@ namespace UserInterface
LmsApp->getModalManager().show(std::move(modal));
});
_container = bindNew<InfiniteScrollingContainer>("tracks");
_container = bindNew<InfiniteScrollingContainer>("tracks", Wt::WString::tr("Lms.Explore.TrackList.template.entry-container"));
_container->onRequestElements.connect([this]
{
addSome();
+1 -1
View File
@@ -86,7 +86,7 @@ Tracks::Tracks(Filters& filters, PlayQueueController& playQueueController)
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, getAllTracks());
});
_container = bindNew<InfiniteScrollingContainer>("tracks");
_container = bindNew<InfiniteScrollingContainer>("tracks", Wt::WString::tr("Lms.Explore.Tracks.template.entry-container"));
_container->onRequestElements.connect([this]
{
addSome();