diff --git a/CMakeLists.txt b/CMakeLists.txt index b14fb02e..d56176ec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(lms) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/) set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_CXX_STANDARD_REQUIRED TRUE) if (UNIX) set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") endif () diff --git a/approot/admin-users.xml b/approot/admin-users.xml index fefea261..1aa0d467 100644 --- a/approot/admin-users.xml +++ b/approot/admin-users.xml @@ -15,7 +15,7 @@ ${name}${}${tag}${}
- ${}${edit-btn class="btn btn-sm btn-warning me-1"} ${del-btn class="btn btn-sm btn-danger"}${} + ${}${edit-btn class="btn btn-sm btn-primary me-1"} ${del-btn class="btn btn-sm btn-danger"}${}
diff --git a/approot/explore.xml b/approot/explore.xml index 5147c609..60628d1e 100644 --- a/approot/explore.xml +++ b/approot/explore.xml @@ -13,25 +13,35 @@ -
-
- - ${type} -
-
- - ${value} -
-
-
- ${add-btn class="btn btn-primary me-1"}${cancel-btn class="btn btn-secondary"} + diff --git a/approot/release.xml b/approot/release.xml index 1ef01ed5..c7eff027 100644 --- a/approot/release.xml +++ b/approot/release.xml @@ -22,12 +22,15 @@
${play-btn class="btn btn-outline-primary me-2"} - -
diff --git a/src/libs/utils/include/utils/String.hpp b/src/libs/utils/include/utils/String.hpp index 9f1d5462..a5d3be43 100644 --- a/src/libs/utils/include/utils/String.hpp +++ b/src/libs/utils/include/utils/String.hpp @@ -31,34 +31,43 @@ namespace StringUtils { +[[nodiscard]] std::vector splitStringCopy(std::string_view string, std::string_view separators); +[[nodiscard]] std::vector splitString(std::string_view string, std::string_view separators); +[[nodiscard]] std::string joinStrings(const std::vector& strings, const std::string& delimiter); +[[nodiscard]] std::string stringTrim(std::string_view str, std::string_view whitespaces = " \t"); +[[nodiscard]] std::string stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t"); +[[nodiscard]] std::string stringToLower(std::string_view str); void stringToLower(std::string& str); +[[nodiscard]] std::string stringToUpper(const std::string& str); +[[nodiscard]] std::string bufferToString(const std::vector& data); template +[[nodiscard]] std::optional readAs(std::string_view str) { T res; @@ -72,21 +81,27 @@ std::optional readAs(std::string_view str) } template<> +[[nodiscard]] std::optional readAs(std::string_view str); +[[nodiscard]] std::string replaceInString(const std::string& str, const std::string& from, const std::string& to); +[[nodiscard]] std::string jsEscape(const std::string& str); +[[nodiscard]] std::string escapeString(std::string_view str, std::string_view charsToEscape, char escapeChar); +[[nodiscard]] bool stringEndsWith(const std::string& str, const std::string& ending); +[[nodiscard]] std::optional stringFromHex(const std::string& str); diff --git a/src/libs/utils/test/String.cpp b/src/libs/utils/test/String.cpp index 9ece8f13..3935f6c7 100644 --- a/src/libs/utils/test/String.cpp +++ b/src/libs/utils/test/String.cpp @@ -24,7 +24,7 @@ TEST(StringUtils, splitString) { { - const std::string test{"a"}; + const std::string test {"a"}; const std::vector strings {StringUtils::splitString(test, "")}; 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 strings {StringUtils::splitString(test, "|")}; 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 strings {StringUtils::splitString(test, " ")}; 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 strings {StringUtils::splitString(test, " ")}; 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 strings {StringUtils::splitString(test, " ")}; 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 strings {StringUtils::splitString(test, " ,|")}; ASSERT_EQ(strings.size(), 4); @@ -76,6 +76,28 @@ TEST(StringUtils, splitString) } } + +TEST(StringUtils, splitStringCopy) +{ + { + const std::string test {"test=foo"}; + + const std::vector 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 strings {StringUtils::splitStringCopy(test, "=")}; + ASSERT_EQ(strings.size(), 2); + EXPECT_EQ(strings[0], "test"); + EXPECT_EQ(strings[1], "foo bar"); + } +} + TEST(StringUtils, escapeString) { EXPECT_EQ(StringUtils::escapeString("", "*", ' '), ""); diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index 7d2d50a7..90e4e477 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(lms ui/common/LoginNameValidator.cpp ui/common/MandatoryValidator.cpp ui/common/PasswordValidator.cpp + ui/common/Template.cpp ui/common/UUIDValidator.cpp ui/explore/ArtistCollector.cpp ui/explore/ArtistListHelpers.cpp @@ -33,7 +34,6 @@ add_executable(lms ui/explore/Filters.cpp ui/explore/ReleaseCollector.cpp ui/explore/ReleaseListHelpers.cpp - ui/explore/ReleasePopup.cpp ui/explore/ReleasesView.cpp ui/explore/ReleaseView.cpp ui/explore/SearchView.cpp diff --git a/src/lms/ui/common/Template.cpp b/src/lms/ui/common/Template.cpp new file mode 100644 index 00000000..a8cdb6a2 --- /dev/null +++ b/src/lms/ui/common/Template.cpp @@ -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 . + */ + +#include "Template.hpp" +#include "utils/String.hpp" + +namespace UserInterface +{ + void + Template::applyArguments(WWidget* widget, const std::vector& args) + { + for (const Wt::WString& arg : args) + { + const std::vector 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 diff --git a/src/lms/ui/explore/ReleasePopup.hpp b/src/lms/ui/common/Template.hpp similarity index 72% rename from src/lms/ui/explore/ReleasePopup.hpp rename to src/lms/ui/common/Template.hpp index 7bf35ad8..6e8cf423 100644 --- a/src/lms/ui/explore/ReleasePopup.hpp +++ b/src/lms/ui/common/Template.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2020 Emeric Poupon + * Copyright (C) 2022 Emeric Poupon * * This file is part of LMS. * @@ -19,15 +19,18 @@ #pragma once -#include - -#include "services/database/Types.hpp" -#include "PlayQueueAction.hpp" +#include 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& args) override; + }; + +} // namespace UserInterface diff --git a/src/lms/ui/explore/ReleasePopup.cpp b/src/lms/ui/explore/ReleasePopup.cpp deleted file mode 100644 index ae8aefcb..00000000 --- a/src/lms/ui/explore/ReleasePopup.cpp +++ /dev/null @@ -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 . - */ - -#include "ReleasePopup.hpp" - -#include - -#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::get()->isStarred(LmsApp->getUserId(), releaseId)}; - popup->addItem(Wt::WString::tr(isStarred ? "Lms.Explore.unstar" : "Lms.Explore.star")) - ->triggered().connect(&target, [=] - { - if (isStarred) - Service::get()->unstar(LmsApp->getUserId(), releaseId); - else - Service::get()->star(LmsApp->getUserId(), releaseId); - }); - popup->addItem(Wt::WString::tr("Lms.Explore.download")) - ->setLink(Wt::WLink {std::make_unique(releaseId)}); - - popup->popup(&target); - } -} // namespace UserInterface diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 96002830..c56fae25 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -32,8 +32,8 @@ #include "services/database/Session.hpp" #include "services/database/Track.hpp" #include "services/recommendation/IRecommendationService.hpp" +#include "services/scrobbling/IScrobblingService.hpp" #include "utils/Logger.hpp" -#include "utils/String.hpp" #include "resource/DownloadResource.hpp" #include "resource/CoverResource.hpp" @@ -42,7 +42,6 @@ #include "LmsApplicationException.hpp" #include "MediaPlayer.hpp" #include "ReleaseListHelpers.hpp" -#include "ReleasePopup.hpp" #include "TrackPopup.hpp" #include "TrackStringUtils.hpp" @@ -51,10 +50,11 @@ using namespace Database; namespace UserInterface { Release::Release(Filters* filters) -: Wt::WTemplate {Wt::WString::tr("Lms.Explore.Release.template")} +: Template {Wt::WString::tr("Lms.Explore.Release.template")} , _filters {filters} { addFunction("tr", &Wt::WTemplate::Functions::tr); + addFunction("id", &Wt::WTemplate::Functions::id); wApp->internalPathChanged().connect(this, [this] { @@ -159,21 +159,45 @@ Release::refreshView() } } - { - Wt::WPushButton* playBtn {bindNew("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)}; - playBtn->clicked().connect([=] + bindNew("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML); + bindNew("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML) + ->clicked().connect([=] { releasesAction.emit(PlayQueueAction::Play, {*releaseId}); }); - } - { - Wt::WPushButton* playShuffled {bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)}; - playShuffled->setDefault(false); - playShuffled->clicked().connect([=] + bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) + ->clicked().connect([=] { releasesAction.emit(PlayQueueAction::PlayShuffled, {*releaseId}); }); + + bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) + ->clicked().connect([=] + { + releasesAction.emit(PlayQueueAction::PlayLast, {*releaseId}); + }); + + bindNew("download", Wt::WString::tr("Lms.Explore.download")) + ->setLink(Wt::WLink {std::make_unique(*releaseId)}); + + { + auto isStarred {[=] { return Service::get()->isStarred(LmsApp->getUserId(), *releaseId); }}; + + Wt::WPushButton* star {bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star"))}; + star->clicked().connect([=] + { + if (isStarred()) + { + Service::get()->unstar(LmsApp->getUserId(), *releaseId); + star->setText(Wt::WString::tr("Lms.Explore.star")); + } + else + { + Service::get()->star(LmsApp->getUserId(), *releaseId); + star->setText(Wt::WString::tr("Lms.Explore.unstar")); + } + }); } Wt::WContainerWidget* rootContainer {bindNew("container")}; @@ -265,12 +289,6 @@ Release::refreshView() tracksAction.emit(PlayQueueAction::Play, {trackId}); }); - Wt::WPushButton* moreBtn {entry->bindNew("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); LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=] (TrackId loadedTrackId) diff --git a/src/lms/ui/explore/ReleaseView.hpp b/src/lms/ui/explore/ReleaseView.hpp index 41f9fbf6..e867874f 100644 --- a/src/lms/ui/explore/ReleaseView.hpp +++ b/src/lms/ui/explore/ReleaseView.hpp @@ -19,10 +19,8 @@ #pragma once -#include -#include - #include "services/database/Object.hpp" +#include "common/Template.hpp" #include "PlayQueueAction.hpp" namespace Database @@ -33,7 +31,9 @@ namespace Database namespace UserInterface { class Filters; - class Release : public Wt::WTemplate + + + class Release : public Template { public: Release(Filters* filters);