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(); }