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_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 ()
+1 -1
View File
@@ -15,7 +15,7 @@
${name}${<if-tag>}<span class="badge bg-info ms-2">${tag}</span>${</if-tag>}
</div>
<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>
</message>
+27 -17
View File
@@ -13,25 +13,35 @@
</message>
<message id="Lms.Explore.template.add-filter">
<form class="row g-3">
<div class="col-12">
<label class="form-label" for="${id:type}">
${tr:Lms.Explore.type}
</label>
${type}
</div>
<div class="col-12">
<label class="form-label" for="${id:value}">
${tr:Lms.Explore.value}
</label>
${value}
</div>
<div class="col-12">
<div class="col-sm-offset-2 col-sm-10">
${add-btn class="btn btn-primary me-1"}${cancel-btn class="btn btn-secondary"}
<div class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${tr:Lms.Explore.add-filter}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<form class="row g-3">
<div class="col-12">
<label class="form-label" for="${id:type}">
${tr:Lms.Explore.type}
</label>
${type class="form-control"}
</div>
<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>
</form>
</div>
</message>
</messages>
+5 -2
View File
@@ -22,12 +22,15 @@
<div class="mb-3">
${play-btn class="btn btn-outline-primary me-2"}
<span class="dropdown">
<button class="btn btn-outline-primary" type="button" id="dropdownMenuButton1" data-bs-toggle="dropdown" aria-expanded="false">...</button>
<ul class="dropdown-menu" aria-labelledby="dropdownMenuButton1">
${more-btn class="btn btn-outline-primary" data-bs-toggle="dropdown" aria-expanded="false"}
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
<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>}
<li><a href="${mbid-link}" target="_blank" class="dropdown-item">${tr:Lms.Explore.musicbrainz-release}</a></li>
${</if-has-mbid>}
<li>${download class="btn dropdown-item"}</li>
</ul>
</span>
</div>
+15
View File
@@ -31,34 +31,43 @@
namespace StringUtils {
[[nodiscard]]
std::vector<std::string>
splitStringCopy(std::string_view string, std::string_view separators);
[[nodiscard]]
std::vector<std::string_view>
splitString(std::string_view string, std::string_view separators);
[[nodiscard]]
std::string
joinStrings(const std::vector<std::string>& 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<unsigned char>& data);
template<typename T>
[[nodiscard]]
std::optional<T> readAs(std::string_view str)
{
T res;
@@ -72,21 +81,27 @@ std::optional<T> readAs(std::string_view str)
}
template<>
[[nodiscard]]
std::optional<std::string>
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<std::string>
stringFromHex(const std::string& str);
+28 -6
View File
@@ -24,7 +24,7 @@
TEST(StringUtils, splitString)
{
{
const std::string test{"a"};
const std::string test {"a"};
const std::vector<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<std::string_view> 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<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)
{
EXPECT_EQ(StringUtils::escapeString("", "*", ' '), "");
+1 -1
View File
@@ -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
+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.
*
@@ -19,15 +19,18 @@
#pragma once
#include <Wt/WWidget.h>
#include "services/database/Types.hpp"
#include "PlayQueueAction.hpp"
#include <Wt/WTemplate.h>
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/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<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
playBtn->clicked().connect([=]
bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML);
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)
->clicked().connect([=]
{
releasesAction.emit(PlayQueueAction::Play, {*releaseId});
});
}
{
Wt::WPushButton* playShuffled {bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)};
playShuffled->setDefault(false);
playShuffled->clicked().connect([=]
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
->clicked().connect([=]
{
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")};
@@ -265,12 +289,6 @@ Release::refreshView()
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);
LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=] (TrackId loadedTrackId)
+4 -4
View File
@@ -19,10 +19,8 @@
#pragma once
#include <unordered_set>
#include <Wt/WTemplate.h>
#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);