Still converting to bootstrap 5. Converted release dropdown

This commit is contained in:
emeric
2022-05-07 14:18:43 +02:00
parent 9ebc5986a1
commit ef793253c7
12 changed files with 170 additions and 124 deletions
+1 -1
View File
@@ -5,7 +5,7 @@ project(lms)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
if (UNIX) if (UNIX)
set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined")
endif () endif ()
+1 -1
View File
@@ -15,7 +15,7 @@
${name}${<if-tag>}<span class="badge bg-info ms-2">${tag}</span>${</if-tag>} ${name}${<if-tag>}<span class="badge bg-info ms-2">${tag}</span>${</if-tag>}
</div> </div>
<div class="col-auto"> <div class="col-auto">
${<if-edit>}${edit-btn class="btn btn-sm btn-warning me-1"} ${del-btn class="btn btn-sm btn-danger"}${</if-edit>} ${<if-edit>}${edit-btn class="btn btn-sm btn-primary me-1"} ${del-btn class="btn btn-sm btn-danger"}${</if-edit>}
</div> </div>
</div> </div>
</message> </message>
+27 -17
View File
@@ -13,25 +13,35 @@
</message> </message>
<message id="Lms.Explore.template.add-filter"> <message id="Lms.Explore.template.add-filter">
<form class="row g-3"> <div class="modal fade" tabindex="-1">
<div class="col-12"> <div class="modal-dialog">
<label class="form-label" for="${id:type}"> <div class="modal-content">
${tr:Lms.Explore.type} <div class="modal-header">
</label> <h5 class="modal-title">${tr:Lms.Explore.add-filter}</h5>
${type} <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<div class="col-12"> <div class="modal-body">
<label class="form-label" for="${id:value}"> <form class="row g-3">
${tr:Lms.Explore.value} <div class="col-12">
</label> <label class="form-label" for="${id:type}">
${value} ${tr:Lms.Explore.type}
</div> </label>
<div class="col-12"> ${type class="form-control"}
<div class="col-sm-offset-2 col-sm-10"> </div>
${add-btn class="btn btn-primary me-1"}${cancel-btn class="btn btn-secondary"} <div class="col-12">
<label class="form-label" for="${id:value}">
${tr:Lms.Explore.value}
</label>
${value class="form-control"}
</div>
</form>
<div class="modal-footer">
${add-btn class="btn btn-primary me-1"}${cancel-btn class="btn btn-secondary"}
</div>
</div>
</div> </div>
</div> </div>
</form> </div>
</message> </message>
</messages> </messages>
+5 -2
View File
@@ -22,12 +22,15 @@
<div class="mb-3"> <div class="mb-3">
${play-btn class="btn btn-outline-primary me-2"} ${play-btn class="btn btn-outline-primary me-2"}
<span class="dropdown"> <span class="dropdown">
<button class="btn btn-outline-primary" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">...</button> ${more-btn class="btn btn-outline-primary" data-bs-toggle="dropdown" aria-expanded="false"}
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1"> <ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
<li>${play-shuffled class="btn dropdown-item"}</li> <li>${play-shuffled class="btn dropdown-item"}</li>
<li>${play-last class="btn dropdown-item"}</li>
<li>${star class="btn dropdown-item"}</li>
${<if-has-mbid>} ${<if-has-mbid>}
<li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-release}</a></li> <li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-release}</a></li>
${</if-has-mbid>} ${</if-has-mbid>}
<li>${download class="btn dropdown-item"}</li>
</ul> </ul>
</span> </span>
</div> </div>
+15
View File
@@ -31,34 +31,43 @@
namespace StringUtils { namespace StringUtils {
[[nodiscard]]
std::vector<std::string> std::vector<std::string>
splitStringCopy(std::string_view string, std::string_view separators); splitStringCopy(std::string_view string, std::string_view separators);
[[nodiscard]]
std::vector<std::string_view> std::vector<std::string_view>
splitString(std::string_view string, std::string_view separators); splitString(std::string_view string, std::string_view separators);
[[nodiscard]]
std::string std::string
joinStrings(const std::vector<std::string>& strings, const std::string& delimiter); joinStrings(const std::vector<std::string>& strings, const std::string& delimiter);
[[nodiscard]]
std::string std::string
stringTrim(std::string_view str, std::string_view whitespaces = " \t"); stringTrim(std::string_view str, std::string_view whitespaces = " \t");
[[nodiscard]]
std::string std::string
stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t"); stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t");
[[nodiscard]]
std::string std::string
stringToLower(std::string_view str); stringToLower(std::string_view str);
void void
stringToLower(std::string& str); stringToLower(std::string& str);
[[nodiscard]]
std::string std::string
stringToUpper(const std::string& str); stringToUpper(const std::string& str);
[[nodiscard]]
std::string std::string
bufferToString(const std::vector<unsigned char>& data); bufferToString(const std::vector<unsigned char>& data);
template<typename T> template<typename T>
[[nodiscard]]
std::optional<T> readAs(std::string_view str) std::optional<T> readAs(std::string_view str)
{ {
T res; T res;
@@ -72,21 +81,27 @@ std::optional<T> readAs(std::string_view str)
} }
template<> template<>
[[nodiscard]]
std::optional<std::string> std::optional<std::string>
readAs(std::string_view str); readAs(std::string_view str);
[[nodiscard]]
std::string std::string
replaceInString(const std::string& str, const std::string& from, const std::string& to); replaceInString(const std::string& str, const std::string& from, const std::string& to);
[[nodiscard]]
std::string std::string
jsEscape(const std::string& str); jsEscape(const std::string& str);
[[nodiscard]]
std::string std::string
escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar); escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar);
[[nodiscard]]
bool bool
stringEndsWith(const std::string& str, const std::string& ending); stringEndsWith(const std::string& str, const std::string& ending);
[[nodiscard]]
std::optional<std::string> std::optional<std::string>
stringFromHex(const std::string& str); stringFromHex(const std::string& str);
+28 -6
View File
@@ -24,7 +24,7 @@
TEST(StringUtils, splitString) TEST(StringUtils, splitString)
{ {
{ {
const std::string test{"a"}; const std::string test {"a"};
const std::vector<std::string_view> strings {StringUtils::splitString(test, "")}; const std::vector<std::string_view> strings {StringUtils::splitString(test, "")};
ASSERT_EQ(strings.size(), 1); ASSERT_EQ(strings.size(), 1);
@@ -32,7 +32,7 @@ TEST(StringUtils, splitString)
} }
{ {
const std::string test{"a b"}; const std::string test {"a b"};
const std::vector<std::string_view> strings {StringUtils::splitString(test, "|")}; const std::vector<std::string_view> strings {StringUtils::splitString(test, "|")};
ASSERT_EQ(strings.size(), 1); ASSERT_EQ(strings.size(), 1);
@@ -40,7 +40,7 @@ TEST(StringUtils, splitString)
} }
{ {
const std::string test{" a"}; const std::string test {" a"};
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")}; const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
ASSERT_EQ(strings.size(), 1); ASSERT_EQ(strings.size(), 1);
@@ -48,7 +48,7 @@ TEST(StringUtils, splitString)
} }
{ {
const std::string test{"a "}; const std::string test {"a "};
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")}; const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
ASSERT_EQ(strings.size(), 1); ASSERT_EQ(strings.size(), 1);
@@ -56,7 +56,7 @@ TEST(StringUtils, splitString)
} }
{ {
const std::string test{"a b"}; const std::string test {"a b"};
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")}; const std::vector<std::string_view> strings {StringUtils::splitString(test, " ")};
ASSERT_EQ(strings.size(), 2); ASSERT_EQ(strings.size(), 2);
@@ -65,7 +65,7 @@ TEST(StringUtils, splitString)
} }
{ {
const std::string test{"a b,c|defgh "}; const std::string test {"a b,c|defgh "};
const std::vector<std::string_view> strings {StringUtils::splitString(test, " ,|")}; const std::vector<std::string_view> strings {StringUtils::splitString(test, " ,|")};
ASSERT_EQ(strings.size(), 4); ASSERT_EQ(strings.size(), 4);
@@ -76,6 +76,28 @@ TEST(StringUtils, splitString)
} }
} }
TEST(StringUtils, splitStringCopy)
{
{
const std::string test {"test=foo"};
const std::vector<std::string> strings {StringUtils::splitStringCopy(test, "=")};
ASSERT_EQ(strings.size(), 2);
EXPECT_EQ(strings[0], "test");
EXPECT_EQ(strings[1], "foo");
}
{
const std::string test {"test=foo bar"};
const std::vector<std::string> strings {StringUtils::splitStringCopy(test, "=")};
ASSERT_EQ(strings.size(), 2);
EXPECT_EQ(strings[0], "test");
EXPECT_EQ(strings[1], "foo bar");
}
}
TEST(StringUtils, escapeString) TEST(StringUtils, escapeString)
{ {
EXPECT_EQ(StringUtils::escapeString("", "*", ' '), ""); EXPECT_EQ(StringUtils::escapeString("", "*", ' '), "");
+1 -1
View File
@@ -23,6 +23,7 @@ add_executable(lms
ui/common/LoginNameValidator.cpp ui/common/LoginNameValidator.cpp
ui/common/MandatoryValidator.cpp ui/common/MandatoryValidator.cpp
ui/common/PasswordValidator.cpp ui/common/PasswordValidator.cpp
ui/common/Template.cpp
ui/common/UUIDValidator.cpp ui/common/UUIDValidator.cpp
ui/explore/ArtistCollector.cpp ui/explore/ArtistCollector.cpp
ui/explore/ArtistListHelpers.cpp ui/explore/ArtistListHelpers.cpp
@@ -33,7 +34,6 @@ add_executable(lms
ui/explore/Filters.cpp ui/explore/Filters.cpp
ui/explore/ReleaseCollector.cpp ui/explore/ReleaseCollector.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
+41
View File
@@ -0,0 +1,41 @@
/*
* Copyright (C) 2022 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 "Template.hpp"
#include "utils/String.hpp"
namespace UserInterface
{
void
Template::applyArguments(WWidget* widget, const std::vector<Wt::WString>& args)
{
for (const Wt::WString& arg : args)
{
const std::vector<std::string> operands {StringUtils::splitStringCopy(arg.toUTF8(), "=")};
if (operands.size() == 2)
{
if (operands[0] == "class")
widget->addStyleClass(operands[1]);
else
widget->setAttributeValue(operands[0], operands[1]);
}
}
}
} // namespace UserInterface
@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2020 Emeric Poupon * Copyright (C) 2022 Emeric Poupon
* *
* This file is part of LMS. * This file is part of LMS.
* *
@@ -19,15 +19,18 @@
#pragma once #pragma once
#include <Wt/WWidget.h> #include <Wt/WTemplate.h>
#include "services/database/Types.hpp"
#include "PlayQueueAction.hpp"
namespace UserInterface namespace UserInterface
{ {
void displayReleasePopupMenu(Wt::WInteractWidget& target,
Database::ReleaseId releaseId,
PlayQueueActionReleaseSignal& releasesAction);
} // namespace UserInterface
class Template : public Wt::WTemplate
{
public:
using Wt::WTemplate::WTemplate;
private:
void applyArguments(Wt::WWidget* w, const std::vector<Wt::WString>& args) override;
};
} // namespace UserInterface
-66
View File
@@ -1,66 +0,0 @@
/*
* 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 <Wt/WPopupMenu.h>
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "resource/DownloadResource.hpp"
#include "utils/Service.hpp"
#include "LmsApplication.hpp"
namespace UserInterface
{
void
displayReleasePopupMenu(Wt::WInteractWidget& target,
Database::ReleaseId releaseId,
PlayQueueActionReleaseSignal& 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});
});
const bool isStarred {Service<Scrobbling::IScrobblingService>::get()->isStarred(LmsApp->getUserId(), releaseId)};
popup->addItem(Wt::WString::tr(isStarred ? "Lms.Explore.unstar" : "Lms.Explore.star"))
->triggered().connect(&target, [=]
{
if (isStarred)
Service<Scrobbling::IScrobblingService>::get()->unstar(LmsApp->getUserId(), releaseId);
else
Service<Scrobbling::IScrobblingService>::get()->star(LmsApp->getUserId(), releaseId);
});
popup->addItem(Wt::WString::tr("Lms.Explore.download"))
->setLink(Wt::WLink {std::make_unique<DownloadReleaseResource>(releaseId)});
popup->popup(&target);
}
} // namespace UserInterface
+35 -17
View File
@@ -32,8 +32,8 @@
#include "services/database/Session.hpp" #include "services/database/Session.hpp"
#include "services/database/Track.hpp" #include "services/database/Track.hpp"
#include "services/recommendation/IRecommendationService.hpp" #include "services/recommendation/IRecommendationService.hpp"
#include "services/scrobbling/IScrobblingService.hpp"
#include "utils/Logger.hpp" #include "utils/Logger.hpp"
#include "utils/String.hpp"
#include "resource/DownloadResource.hpp" #include "resource/DownloadResource.hpp"
#include "resource/CoverResource.hpp" #include "resource/CoverResource.hpp"
@@ -42,7 +42,6 @@
#include "LmsApplicationException.hpp" #include "LmsApplicationException.hpp"
#include "MediaPlayer.hpp" #include "MediaPlayer.hpp"
#include "ReleaseListHelpers.hpp" #include "ReleaseListHelpers.hpp"
#include "ReleasePopup.hpp"
#include "TrackPopup.hpp" #include "TrackPopup.hpp"
#include "TrackStringUtils.hpp" #include "TrackStringUtils.hpp"
@@ -51,10 +50,11 @@ using namespace Database;
namespace UserInterface { namespace UserInterface {
Release::Release(Filters* filters) Release::Release(Filters* filters)
: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Release.template")} : Template {Wt::WString::tr("Lms.Explore.Release.template")}
, _filters {filters} , _filters {filters}
{ {
addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("id", &Wt::WTemplate::Functions::id);
wApp->internalPathChanged().connect(this, [this] wApp->internalPathChanged().connect(this, [this]
{ {
@@ -159,21 +159,45 @@ Release::refreshView()
} }
} }
{ bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML);
Wt::WPushButton* playBtn {bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)}; bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)
playBtn->clicked().connect([=] ->clicked().connect([=]
{ {
releasesAction.emit(PlayQueueAction::Play, {*releaseId}); releasesAction.emit(PlayQueueAction::Play, {*releaseId});
}); });
}
{ bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
Wt::WPushButton* playShuffled {bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)}; ->clicked().connect([=]
playShuffled->setDefault(false);
playShuffled->clicked().connect([=]
{ {
releasesAction.emit(PlayQueueAction::PlayShuffled, {*releaseId}); releasesAction.emit(PlayQueueAction::PlayShuffled, {*releaseId});
}); });
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
releasesAction.emit(PlayQueueAction::PlayLast, {*releaseId});
});
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
->setLink(Wt::WLink {std::make_unique<DownloadReleaseResource>(*releaseId)});
{
auto isStarred {[=] { return Service<Scrobbling::IScrobblingService>::get()->isStarred(LmsApp->getUserId(), *releaseId); }};
Wt::WPushButton* star {bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star"))};
star->clicked().connect([=]
{
if (isStarred())
{
Service<Scrobbling::IScrobblingService>::get()->unstar(LmsApp->getUserId(), *releaseId);
star->setText(Wt::WString::tr("Lms.Explore.star"));
}
else
{
Service<Scrobbling::IScrobblingService>::get()->star(LmsApp->getUserId(), *releaseId);
star->setText(Wt::WString::tr("Lms.Explore.unstar"));
}
});
} }
Wt::WContainerWidget* rootContainer {bindNew<Wt::WContainerWidget>("container")}; Wt::WContainerWidget* rootContainer {bindNew<Wt::WContainerWidget>("container")};
@@ -265,12 +289,6 @@ Release::refreshView()
tracksAction.emit(PlayQueueAction::Play, {trackId}); tracksAction.emit(PlayQueueAction::Play, {trackId});
}); });
Wt::WPushButton* moreBtn {entry->bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)};
moreBtn->clicked().connect([=]
{
displayTrackPopupMenu(*moreBtn, trackId, tracksAction);
});
entry->bindString("duration", durationToString(track->getDuration()), Wt::TextFormat::Plain); entry->bindString("duration", durationToString(track->getDuration()), Wt::TextFormat::Plain);
LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=] (TrackId loadedTrackId) LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=] (TrackId loadedTrackId)
+4 -4
View File
@@ -19,10 +19,8 @@
#pragma once #pragma once
#include <unordered_set>
#include <Wt/WTemplate.h>
#include "services/database/Object.hpp" #include "services/database/Object.hpp"
#include "common/Template.hpp"
#include "PlayQueueAction.hpp" #include "PlayQueueAction.hpp"
namespace Database namespace Database
@@ -33,7 +31,9 @@ namespace Database
namespace UserInterface namespace UserInterface
{ {
class Filters; class Filters;
class Release : public Wt::WTemplate
class Release : public Template
{ {
public: public:
Release(Filters* filters); Release(Filters* filters);