Still converting to bootstrap 5. Made notification system work again

This commit is contained in:
emeric
2022-04-19 17:07:04 +02:00
parent a354e72896
commit 6046da7a4c
15 changed files with 140 additions and 53 deletions
+1
View File
@@ -5,6 +5,7 @@
${navbar} ${navbar}
<div class="container"> <div class="container">
${contents} ${contents}
${notifications class="toast-container position-fixed top-0 end-0 p-3"}
</div> </div>
${player class="fixed-bottom bg-light Lms-player"} ${player class="fixed-bottom bg-light Lms-player"}
</message> </message>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<message id="Lms.notifications.template.entry">
<div class="toast align-items-center ${bg-color} text-${text-color} border-0" role="alert" aria-live="assertive" aria-atomic="true" data-bs-delay="${duration}">
<div class="d-flex">
<div class="toast-body">
${message}
</div>
<button type="button" class="btn-close btn-close-${text-color} me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
</message>
</messages>
+1
View File
@@ -6,6 +6,7 @@ add_executable(lms
ui/LmsApplicationManager.cpp ui/LmsApplicationManager.cpp
ui/LmsTheme.cpp ui/LmsTheme.cpp
ui/MediaPlayer.cpp ui/MediaPlayer.cpp
ui/NotificationContainer.cpp
ui/PlayQueue.cpp ui/PlayQueue.cpp
ui/SettingsView.cpp ui/SettingsView.cpp
ui/TrackStringUtils.cpp ui/TrackStringUtils.cpp
+10 -34
View File
@@ -58,6 +58,7 @@
#include "LmsApplicationManager.hpp" #include "LmsApplicationManager.hpp"
#include "LmsTheme.hpp" #include "LmsTheme.hpp"
#include "MediaPlayer.hpp" #include "MediaPlayer.hpp"
#include "NotificationContainer.hpp"
#include "PlayQueue.hpp" #include "PlayQueue.hpp"
#include "SettingsView.hpp" #include "SettingsView.hpp"
@@ -186,6 +187,7 @@ LmsApplication::init()
messageResourceBundle().use(appRoot() + "mediaplayer"); messageResourceBundle().use(appRoot() + "mediaplayer");
messageResourceBundle().use(appRoot() + "messages"); messageResourceBundle().use(appRoot() + "messages");
messageResourceBundle().use(appRoot() + "misc"); messageResourceBundle().use(appRoot() + "misc");
messageResourceBundle().use(appRoot() + "notifications");
messageResourceBundle().use(appRoot() + "playqueue"); messageResourceBundle().use(appRoot() + "playqueue");
messageResourceBundle().use(appRoot() + "release"); messageResourceBundle().use(appRoot() + "release");
messageResourceBundle().use(appRoot() + "releases"); messageResourceBundle().use(appRoot() + "releases");
@@ -460,6 +462,8 @@ LmsApplication::createHome()
Wt::WTemplate* navbar {main->bindNew<Wt::WTemplate>("navbar", Wt::WString::tr("Lms.main.template.navbar"))}; Wt::WTemplate* navbar {main->bindNew<Wt::WTemplate>("navbar", Wt::WString::tr("Lms.main.template.navbar"))};
_notificationContainer = main->bindNew<NotificationContainer>("notifications");
// MediaPlayer // MediaPlayer
_mediaPlayer = main->bindNew<MediaPlayer>("player"); _mediaPlayer = main->bindNew<MediaPlayer>("player");
@@ -566,7 +570,9 @@ LmsApplication::createHome()
{ {
_scannerEvents.scanComplete.connect([=] (const Scanner::ScanStats& stats) _scannerEvents.scanComplete.connect([=] (const Scanner::ScanStats& stats)
{ {
notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-complete") notifyMsg(Notification::Type::Info,
Wt::WString::tr("Lms.Admin.Database.database"),
Wt::WString::tr("Lms.Admin.Database.scan-complete")
.arg(static_cast<unsigned>(stats.nbFiles())) .arg(static_cast<unsigned>(stats.nbFiles()))
.arg(static_cast<unsigned>(stats.additions)) .arg(static_cast<unsigned>(stats.additions))
.arg(static_cast<unsigned>(stats.updates)) .arg(static_cast<unsigned>(stats.updates))
@@ -603,49 +609,19 @@ LmsApplication::notify(const Wt::WEvent& event)
} }
} }
static std::string msgTypeToString(LmsApplication::MsgType type)
{
switch(type)
{
case LmsApplication::MsgType::Success: return "success";
case LmsApplication::MsgType::Info: return "info";
case LmsApplication::MsgType::Warning: return "warning";
case LmsApplication::MsgType::Danger: return "danger";
}
return "";
}
void void
LmsApplication::post(std::function<void()> func) LmsApplication::post(std::function<void()> func)
{ {
Wt::WServer::instance()->post(LmsApp->sessionId(), std::move(func)); Wt::WServer::instance()->post(LmsApp->sessionId(), std::move(func));
} }
void void
LmsApplication::notifyMsg(MsgType type, const Wt::WString& message, std::chrono::milliseconds duration) LmsApplication::notifyMsg(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration)
{ {
LMS_LOG(UI, INFO) << "Notifying message '" << message.toUTF8() << "' of type '" << msgTypeToString(type) << "'"; LMS_LOG(UI, INFO) << "Notifying message '" << message.toUTF8() << "' for category '" << category.toUTF8() << "'";
_notificationContainer->add(type, category, message, duration);
// TODO
return;
std::ostringstream oss;
oss << "$.notify({"
"message: '" << StringUtils::jsEscape(message.toUTF8()) << "'"
"},{"
"type: '" << msgTypeToString(type) << "',"
"placement: {from: 'bottom', align: 'right'},"
"timer: 250,"
"offset: {x: 20, y: 80},"
"delay: " << duration.count() << ""
"});";
LmsApp->doJavaScript(oss.str());
} }
} // namespace UserInterface } // namespace UserInterface
+4 -8
View File
@@ -27,6 +27,7 @@
#include "services/database/UserId.hpp" #include "services/database/UserId.hpp"
#include "services/database/Types.hpp" #include "services/database/Types.hpp"
#include "services/scanner/ScannerEvents.hpp" #include "services/scanner/ScannerEvents.hpp"
#include "Notification.hpp"
namespace Database namespace Database
{ {
@@ -49,6 +50,7 @@ class LmsApplicationException;
class MediaPlayer; class MediaPlayer;
class PlayQueue; class PlayQueue;
class LmsApplicationManager; class LmsApplicationManager;
class NotificationContainer;
class LmsApplication : public Wt::WApplication class LmsApplication : public Wt::WApplication
{ {
@@ -78,14 +80,7 @@ class LmsApplication : public Wt::WApplication
void post(std::function<void()> func); void post(std::function<void()> func);
// Used to classify the message sent to the user // Used to classify the message sent to the user
enum class MsgType void notifyMsg(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {5000});
{
Success,
Info,
Warning,
Danger,
};
void notifyMsg(MsgType type, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {4000});
static Wt::WLink createArtistLink(Database::ObjectPtr<Database::Artist> artist); 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 std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::ObjectPtr<Database::Artist> artist, bool addText = true);
@@ -129,6 +124,7 @@ class LmsApplication : public Wt::WApplication
MediaPlayer* _mediaPlayer {}; MediaPlayer* _mediaPlayer {};
PlayQueue* _playQueue {}; PlayQueue* _playQueue {};
std::unique_ptr<Wt::WPopupMenu> _popupMenu; std::unique_ptr<Wt::WPopupMenu> _popupMenu;
NotificationContainer* _notificationContainer {};
}; };
+11
View File
@@ -0,0 +1,11 @@
#pragma once
namespace UserInterface::Notification
{
enum class Type
{
Info,
Warning,
Danger,
};
}
+67
View File
@@ -0,0 +1,67 @@
#include "NotificationContainer.hpp"
#include <sstream>
#include <Wt/WTemplate.h>
#include "utils/Logger.hpp"
#include "LmsApplication.hpp"
namespace UserInterface
{
class NotificationWidget : public Wt::WTemplate
{
public:
NotificationWidget(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration);
Wt::JSignal<> closed {this, "closed"};
};
NotificationWidget::NotificationWidget(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration)
: Wt::WTemplate {Wt::WString::tr("Lms.notifications.template.entry")}
{
switch (type)
{
case Notification::Type::Info:
bindString("bg-color", "bg-primary");
bindString("text-color", "white");
break;
case Notification::Type::Warning:
bindString("bg-color", "bg-warning");
bindString("text-color", "dark");
break;
case Notification::Type::Danger:
bindString("bg-color", "bg-danger");
bindString("text-color", "white");
break;
}
bindString("category", category);
bindString("message", message);
bindInt("duration", duration.count());
std::ostringstream oss;
oss
<< R"({const toastElement = )" << jsRef() << R"(.getElementsByClassName('toast')[0];)"
<< R"(const toast = bootstrap.Toast.getOrCreateInstance(toastElement);)"
<< R"(toast.show();)"
<< R"(toastElement.addEventListener('hidden.bs.toast', function () {)"
<< closed.createCall({})
<< R"(toast.dispose();)"
<< R"(});})";
LMS_LOG(UI, DEBUG) << "Running JS '" << oss.str() << "'";
doJavaScript(oss.str());
}
void
NotificationContainer::add(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration)
{
NotificationWidget* notification {addNew<NotificationWidget>(type, category, message, duration)};
notification->closed.connect([=]
{
removeWidget(notification);
});
}
}
+18
View File
@@ -0,0 +1,18 @@
#pragma once
#include <chrono>
#include <Wt/WContainerWidget.h>
#include <Wt/WString.h>
#include "Notification.hpp"
namespace UserInterface
{
class NotificationContainer : public Wt::WContainerWidget
{
public:
void add(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration);
private:
};
} // namespace UserInterface
+2 -4
View File
@@ -383,12 +383,10 @@ PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::Tra
} }
if (nbAddedTracks > 0) if (nbAddedTracks > 0)
LmsApp->notifyMsg(LmsApplication::MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", nbAddedTracks).arg(nbAddedTracks), std::chrono::milliseconds(2000)); LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.PlayQueue.playqueue"), Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", nbAddedTracks).arg(nbAddedTracks), std::chrono::milliseconds(2000));
if (isFull()) if (isFull())
LmsApp->notifyMsg(LmsApplication::MsgType::Warning, Wt::WString::tr("Lms.PlayQueue.playqueue-full"), std::chrono::milliseconds(2000)); LmsApp->notifyMsg(Notification::Type::Warning, Wt::WString::tr("Lms.PlayQueue.playqueue"), Wt::WString::tr("Lms.PlayQueue.playqueue-full"), std::chrono::milliseconds(2000));
} }
void void
+2 -2
View File
@@ -564,7 +564,7 @@ SettingsView::refreshView()
if (LmsApp->getUser()->isDemo()) if (LmsApp->getUser()->isDemo())
{ {
LmsApp->notifyMsg(LmsApplication::MsgType::Warning, Wt::WString::tr("Lms.Settings.demo-cannot-save")); LmsApp->notifyMsg(Notification::Type::Warning, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.demo-cannot-save"));
return; return;
} }
} }
@@ -574,7 +574,7 @@ SettingsView::refreshView()
if (model->validate()) if (model->validate())
{ {
model->saveData(); model->saveData();
LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Settings.settings-saved")); LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.settings-saved"));
} }
// Udate the view: Delete any validation message in the view, etc. // Udate the view: Delete any validation message in the view, etc.
+1 -1
View File
@@ -241,7 +241,7 @@ DatabaseSettingsView::refreshView()
model->saveData(); model->saveData();
Service<Scanner::IScannerService>::get()->requestImmediateScan(false); Service<Scanner::IScannerService>::get()->requestImmediateScan(false);
LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved")); LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.settings-saved"));
} }
// Udate the view: Delete any validation message in the view, etc. // Udate the view: Delete any validation message in the view, etc.
+2 -1
View File
@@ -134,7 +134,8 @@ InitWizardView::InitWizardView()
if (model->validate()) if (model->validate())
{ {
model->saveData(); model->saveData();
LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Admin.InitWizard.done")); // TODO notify here
// LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Admin.InitWizard.done"));
saveButton->setEnabled(false); saveButton->setEnabled(false);
} }
+1 -1
View File
@@ -142,7 +142,7 @@ ScannerController::ScannerController()
LmsApp->getScannerEvents().scanStarted.connect(this, [] LmsApp->getScannerEvents().scanStarted.connect(this, []
{ {
LmsApp->notifyMsg(LmsApplication::MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-launched")); LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.scan-launched"));
}); });
LmsApp->getScannerEvents().scanComplete.connect(this, onDbEvent); LmsApp->getScannerEvents().scanComplete.connect(this, onDbEvent);
LmsApp->getScannerEvents().scanInProgress.connect(this, onDbEvent); LmsApp->getScannerEvents().scanInProgress.connect(this, onDbEvent);
+3 -1
View File
@@ -252,7 +252,9 @@ UserView::refreshView()
if (model->validate()) if (model->validate())
{ {
model->saveData(); model->saveData();
LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr(userId ? "Lms.Admin.User.user-updated" : "Lms.Admin.User.user-created")); LmsApp->notifyMsg(Notification::Type::Info,
Wt::WString::tr("Lms.Admin.Users.users"),
Wt::WString::tr(userId ? "Lms.Admin.User.user-updated" : "Lms.Admin.User.user-created"));
LmsApp->setInternalPath("/admin/users", true); LmsApp->setInternalPath("/admin/users", true);
} }
else else
+3 -1
View File
@@ -155,7 +155,9 @@ Filters::add(ClusterId clusterId)
_sigUpdated.emit(); _sigUpdated.emit();
}); });
LmsApp->notifyMsg(LmsApplication::MsgType::Info, Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds {2}); LmsApp->notifyMsg(Notification::Type::Info,
Wt::WString::tr("Lms.Explore.filters"),
Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds {2});
_sigUpdated.emit(); _sigUpdated.emit();
} }