diff --git a/approot/main.xml b/approot/main.xml
index 8e7655cf..037ec650 100644
--- a/approot/main.xml
+++ b/approot/main.xml
@@ -5,6 +5,7 @@
${navbar}
${contents}
+ ${notifications class="toast-container position-fixed top-0 end-0 p-3"}
${player class="fixed-bottom bg-light Lms-player"}
diff --git a/approot/notifications.xml b/approot/notifications.xml
new file mode 100644
index 00000000..a353b77f
--- /dev/null
+++ b/approot/notifications.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt
index 33dad4b1..379b0e56 100644
--- a/src/lms/CMakeLists.txt
+++ b/src/lms/CMakeLists.txt
@@ -6,6 +6,7 @@ add_executable(lms
ui/LmsApplicationManager.cpp
ui/LmsTheme.cpp
ui/MediaPlayer.cpp
+ ui/NotificationContainer.cpp
ui/PlayQueue.cpp
ui/SettingsView.cpp
ui/TrackStringUtils.cpp
diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp
index d151a7cc..bad83de6 100644
--- a/src/lms/ui/LmsApplication.cpp
+++ b/src/lms/ui/LmsApplication.cpp
@@ -58,6 +58,7 @@
#include "LmsApplicationManager.hpp"
#include "LmsTheme.hpp"
#include "MediaPlayer.hpp"
+#include "NotificationContainer.hpp"
#include "PlayQueue.hpp"
#include "SettingsView.hpp"
@@ -186,6 +187,7 @@ LmsApplication::init()
messageResourceBundle().use(appRoot() + "mediaplayer");
messageResourceBundle().use(appRoot() + "messages");
messageResourceBundle().use(appRoot() + "misc");
+ messageResourceBundle().use(appRoot() + "notifications");
messageResourceBundle().use(appRoot() + "playqueue");
messageResourceBundle().use(appRoot() + "release");
messageResourceBundle().use(appRoot() + "releases");
@@ -460,6 +462,8 @@ LmsApplication::createHome()
Wt::WTemplate* navbar {main->bindNew("navbar", Wt::WString::tr("Lms.main.template.navbar"))};
+ _notificationContainer = main->bindNew("notifications");
+
// MediaPlayer
_mediaPlayer = main->bindNew("player");
@@ -566,7 +570,9 @@ LmsApplication::createHome()
{
_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(stats.nbFiles()))
.arg(static_cast(stats.additions))
.arg(static_cast(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
LmsApplication::post(std::function func)
{
Wt::WServer::instance()->post(LmsApp->sessionId(), std::move(func));
}
-
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) << "'";
-
- // 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());
+ LMS_LOG(UI, INFO) << "Notifying message '" << message.toUTF8() << "' for category '" << category.toUTF8() << "'";
+ _notificationContainer->add(type, category, message, duration);
}
-
} // namespace UserInterface
diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp
index b107686d..86df1a6a 100644
--- a/src/lms/ui/LmsApplication.hpp
+++ b/src/lms/ui/LmsApplication.hpp
@@ -27,6 +27,7 @@
#include "services/database/UserId.hpp"
#include "services/database/Types.hpp"
#include "services/scanner/ScannerEvents.hpp"
+#include "Notification.hpp"
namespace Database
{
@@ -49,6 +50,7 @@ class LmsApplicationException;
class MediaPlayer;
class PlayQueue;
class LmsApplicationManager;
+class NotificationContainer;
class LmsApplication : public Wt::WApplication
{
@@ -78,14 +80,7 @@ class LmsApplication : public Wt::WApplication
void post(std::function func);
// Used to classify the message sent to the user
- enum class MsgType
- {
- Success,
- Info,
- Warning,
- Danger,
- };
- void notifyMsg(MsgType type, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {4000});
+ void notifyMsg(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {5000});
static Wt::WLink createArtistLink(Database::ObjectPtr artist);
static std::unique_ptr createArtistAnchor(Database::ObjectPtr artist, bool addText = true);
@@ -129,6 +124,7 @@ class LmsApplication : public Wt::WApplication
MediaPlayer* _mediaPlayer {};
PlayQueue* _playQueue {};
std::unique_ptr _popupMenu;
+ NotificationContainer* _notificationContainer {};
};
diff --git a/src/lms/ui/Notification.hpp b/src/lms/ui/Notification.hpp
new file mode 100644
index 00000000..4141b7f6
--- /dev/null
+++ b/src/lms/ui/Notification.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+namespace UserInterface::Notification
+{
+ enum class Type
+ {
+ Info,
+ Warning,
+ Danger,
+ };
+}
diff --git a/src/lms/ui/NotificationContainer.cpp b/src/lms/ui/NotificationContainer.cpp
new file mode 100644
index 00000000..10093434
--- /dev/null
+++ b/src/lms/ui/NotificationContainer.cpp
@@ -0,0 +1,67 @@
+#include "NotificationContainer.hpp"
+
+#include
+#include
+
+#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(type, category, message, duration)};
+
+ notification->closed.connect([=]
+ {
+ removeWidget(notification);
+ });
+ }
+}
diff --git a/src/lms/ui/NotificationContainer.hpp b/src/lms/ui/NotificationContainer.hpp
new file mode 100644
index 00000000..78d628f9
--- /dev/null
+++ b/src/lms/ui/NotificationContainer.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+#include
+#include
+#include
+
+#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
diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp
index 3bc7c4ee..d421f811 100644
--- a/src/lms/ui/PlayQueue.cpp
+++ b/src/lms/ui/PlayQueue.cpp
@@ -383,12 +383,10 @@ PlayQueue::processTracks(PlayQueueAction action, const std::vector 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())
- 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
diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp
index 1e35234a..c704314d 100644
--- a/src/lms/ui/SettingsView.cpp
+++ b/src/lms/ui/SettingsView.cpp
@@ -564,7 +564,7 @@ SettingsView::refreshView()
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;
}
}
@@ -574,7 +574,7 @@ SettingsView::refreshView()
if (model->validate())
{
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.
diff --git a/src/lms/ui/admin/DatabaseSettingsView.cpp b/src/lms/ui/admin/DatabaseSettingsView.cpp
index 53cec972..9a145c07 100644
--- a/src/lms/ui/admin/DatabaseSettingsView.cpp
+++ b/src/lms/ui/admin/DatabaseSettingsView.cpp
@@ -241,7 +241,7 @@ DatabaseSettingsView::refreshView()
model->saveData();
Service::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.
diff --git a/src/lms/ui/admin/InitWizardView.cpp b/src/lms/ui/admin/InitWizardView.cpp
index efb14db4..e2e426f5 100644
--- a/src/lms/ui/admin/InitWizardView.cpp
+++ b/src/lms/ui/admin/InitWizardView.cpp
@@ -134,7 +134,8 @@ InitWizardView::InitWizardView()
if (model->validate())
{
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);
}
diff --git a/src/lms/ui/admin/ScannerController.cpp b/src/lms/ui/admin/ScannerController.cpp
index 6b5153db..e42e2e9b 100644
--- a/src/lms/ui/admin/ScannerController.cpp
+++ b/src/lms/ui/admin/ScannerController.cpp
@@ -142,7 +142,7 @@ ScannerController::ScannerController()
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().scanInProgress.connect(this, onDbEvent);
diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp
index 218526f1..7baaf313 100644
--- a/src/lms/ui/admin/UserView.cpp
+++ b/src/lms/ui/admin/UserView.cpp
@@ -252,7 +252,9 @@ UserView::refreshView()
if (model->validate())
{
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);
}
else
diff --git a/src/lms/ui/explore/Filters.cpp b/src/lms/ui/explore/Filters.cpp
index b5807b1a..43756385 100644
--- a/src/lms/ui/explore/Filters.cpp
+++ b/src/lms/ui/explore/Filters.cpp
@@ -155,7 +155,9 @@ Filters::add(ClusterId clusterId)
_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();
}