Still converting to bootstrap 5. Moved away from Wt's boostrap5 theme to better stick to vanilla bootstrap

This commit is contained in:
emeric
2022-05-06 13:22:08 +02:00
parent 582e6ed7ae
commit 147e174315
30 changed files with 437 additions and 98 deletions
+2
View File
@@ -2,10 +2,12 @@
add_executable(lms
main.cpp
ui/Auth.cpp
ui/Bootstrap5Theme.cpp
ui/LmsApplication.cpp
ui/LmsApplicationManager.cpp
ui/LmsTheme.cpp
ui/MediaPlayer.cpp
ui/ModalManager.cpp
ui/NotificationContainer.cpp
ui/PlayQueue.cpp
ui/SettingsView.cpp
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 Emweb bv, Herent, Belgium.
*
* See the LICENSE file for terms of use.
*/
#include "Bootstrap5Theme.h"
#include <Wt/WApplication.h>
#include <Wt/WLinkedCssStyleSheet.h>
#include <Wt/WText.h>
namespace UserInterface
{
void
Bootstrap5Theme::init(Wt::WApplication* app) const
{
app->require(resourcesUrl() + "js/bootstrap.bundle.min.js");
Wt::WString v = app->metaHeader(Wt::MetaHeaderType::Meta, "viewport");
if (v.empty())
app->addMetaHeader("viewport", "width=device-width, initial-scale=1");
}
std::string
Bootstrap5Theme::name() const
{
return "bootstrap5";
}
std::string
Bootstrap5Theme::resourcesUrl() const
{
return Wt::WApplication::relativeResourcesUrl() + "themes/bootstrap/5/";
}
std::vector<Wt::WLinkedCssStyleSheet>
Bootstrap5Theme::styleSheets() const
{
std::vector<Wt::WLinkedCssStyleSheet> result;
const std::string themeDir {resourcesUrl()};
result.emplace_back(Wt::WLink {themeDir + "css/bootstrap.min.css"});
result.emplace_back(Wt::WLink {themeDir + "wt.css"});
return result;
}
std::string
Bootstrap5Theme::utilityCssClass(int utilityCssClassRole) const
{
switch (utilityCssClassRole) {
case Wt::ToolTipInner:
return "tooltip-inner";
case Wt::ToolTipOuter:
return "tooltip fade top in";
default:
return "";
}
}
void Bootstrap5Theme::applyValidationStyle(Wt::WWidget* widget, const Wt::WValidator::Result& validation, Wt::WFlags<Wt::ValidationStyleFlag> styles) const
{
{
const bool validStyle {(validation.state() == Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::ValidStyle)};
widget->toggleStyleClass("is-valid", validStyle);
}
{
const bool invalidStyle {(validation.state() != Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::InvalidStyle)};
widget->toggleStyleClass("is-invalid", invalidStyle);
}
}
} // namespace UserInterface
+45
View File
@@ -0,0 +1,45 @@
/*
* 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/>.
*/
#pragma once
#include <Wt/WTheme.h>
namespace UserInterface
{
class Bootstrap5Theme : public Wt::WTheme
{
private:
void init(Wt::WApplication *app) const override;
std::string name() const override;
std::string resourcesUrl() const override;
std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const override;
void apply(Wt::WWidget*, Wt::WWidget*, int) const override {};
void apply(Wt::WWidget*, Wt::DomElement&, int) const override {};
std::string disabledClass() const override { return "disabled"; }
std::string activeClass() const override { return "active"; };
std::string utilityCssClass(int utilityCssClassRole) const override;
bool canStyleAnchorAsButton() const override { return true; };
void applyValidationStyle(Wt::WWidget* widget,
const Wt::WValidator::Result& validation,
Wt::WFlags<Wt::ValidationStyleFlag> flags) const override;
bool canBorderBoxElement(const Wt::DomElement&) const override { return true; }
};
}
+2
View File
@@ -58,6 +58,7 @@
#include "LmsApplicationManager.hpp"
#include "LmsTheme.hpp"
#include "MediaPlayer.hpp"
#include "ModalManager.hpp"
#include "NotificationContainer.hpp"
#include "PlayQueue.hpp"
#include "SettingsView.hpp"
@@ -462,6 +463,7 @@ LmsApplication::createHome()
Wt::WTemplate* navbar {main->bindNew<Wt::WTemplate>("navbar", Wt::WString::tr("Lms.main.template.navbar"))};
_notificationContainer = main->bindNew<NotificationContainer>("notifications");
_modalManager = main->bindNew<ModalManager>("modal");
// MediaPlayer
_mediaPlayer = main->bindNew<MediaPlayer>("player");
+3
View File
@@ -51,6 +51,7 @@ class MediaPlayer;
class PlayQueue;
class LmsApplicationManager;
class NotificationContainer;
class ModalManager;
class LmsApplication : public Wt::WApplication
{
@@ -91,6 +92,7 @@ class LmsApplication : public Wt::WApplication
MediaPlayer& getMediaPlayer() const { return *_mediaPlayer; }
PlayQueue& getPlayQueue() const { return *_playQueue; }
ModalManager& getModalManager() const { return *_modalManager; }
// Signal emitted just before the session ends (user may already be logged out)
Wt::Signal<>& preQuit() { return _preQuit; }
@@ -125,6 +127,7 @@ class LmsApplication : public Wt::WApplication
PlayQueue* _playQueue {};
std::unique_ptr<Wt::WPopupMenu> _popupMenu;
NotificationContainer* _notificationContainer {};
ModalManager* _modalManager {};
};
-1
View File
@@ -30,7 +30,6 @@ namespace UserInterface
static const std::vector<Wt::WLinkedCssStyleSheet> files
{
{"css/bootstrap.solar.min.css"},
{"resources/themes/bootstrap/5/wt.min.css"},
{"css/lms.css"},
};
+3 -3
View File
@@ -20,13 +20,13 @@
#pragma once
#include <vector>
#include <Wt/WBootstrap5Theme.h>
#include <Wt/WLinkedCssStyleSheet.h>
#include "Bootstrap5Theme.h"
namespace UserInterface
{
class LmsTheme : public Wt::WBootstrap5Theme
class LmsTheme : public Bootstrap5Theme
{
private:
std::vector<Wt::WLinkedCssStyleSheet> styleSheets() const override;
+83
View File
@@ -0,0 +1,83 @@
/*
* 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 "ModalManager.hpp"
#include "utils/Logger.hpp"
namespace UserInterface
{
ModalManager::ModalManager()
: _closed {this, "closed"}
{
_closed.connect([=](const std::string& id)
{
LMS_LOG(UI, DEBUG) << "Received closed for id '" << id << "'";
for (int i {}; i < count(); ++i)
{
Wt::WWidget* widget {this->widget(i)};
LMS_LOG(UI, DEBUG) << "Widget " << i << ", id = '" << widget->id();
if (widget->id() == id)
{
removeWidget(widget);
break;
}
}
});
}
void
ModalManager::show(std::unique_ptr<Wt::WWidget> modalWidget)
{
assert(count() == 0);
LMS_LOG(UI, DEBUG) << "Want to show, id = " << modalWidget->id();
std::ostringstream oss;
oss
<< R"({const modalElement = )" << jsRef() << R"(.getElementsByClassName('modal')[0];)"
<< R"(const modal = bootstrap.Modal.getOrCreateInstance(modalElement);)"
<< R"(modal.show();)"
<< R"(modalElement.addEventListener('hidden.bs.modal', function () {)"
<< _closed.createCall({"'" + modalWidget->id() + "'"})
<< R"(modal.dispose();)"
<< R"(});})";
LMS_LOG(UI, DEBUG) << "Running JS '" << oss.str() << "'";
doJavaScript(oss.str());
addWidget(move(modalWidget));
}
void
ModalManager::dispose(Wt::WWidget* modalWidget)
{
std::ostringstream oss;
oss
<< R"({const modalElementParent = document.getElementById(')" << modalWidget->id() << R"(');)"
<< R"(const modalElement = modalElementParent.getElementsByClassName('modal')[0];)"
<< R"(const modal = bootstrap.Modal.getInstance(modalElement);)"
<< R"(modal.hide();)"
<< R"(})";
LMS_LOG(UI, DEBUG) << "Running JS '" << oss.str() << "'";
doJavaScript(oss.str());
}
}
+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/>.
*/
#pragma once
#include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h>
#include <Wt/WWidget.h>
namespace UserInterface
{
class ModalManager : public Wt::WContainerWidget
{
public:
ModalManager();
// should handle only one modal at a time
// Widget must contain a "modal" element
void show(std::unique_ptr<Wt::WWidget> modalWidget);
void dispose(Wt::WWidget* modalWidget);
private:
Wt::JSignal<std::string> _closed;
};
}
+19
View File
@@ -1,3 +1,22 @@
/*
* 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 "NotificationContainer.hpp"
#include <sstream>
+19
View File
@@ -1,3 +1,22 @@
/*
* 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/>.
*/
#pragma once
#include <chrono>
+20 -18
View File
@@ -30,6 +30,7 @@
#include "utils/Service.hpp"
#include "LmsApplication.hpp"
#include "ModalManager.hpp"
namespace UserInterface {
@@ -101,30 +102,31 @@ UsersView::refreshView()
Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"));
delBtn->clicked().connect([=]
{
auto msgBox = delBtn->addChild(std::make_unique<Wt::WMessageBox>(Wt::WString::tr("Lms.Admin.Users.del-user"),
Wt::WString::tr("Lms.Admin.Users.del-user-name"),
Wt::Icon::Warning, Wt::StandardButton::Yes | Wt::StandardButton::No));
auto modal {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.delete-user"))};
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
Wt::WWidget* modalPtr {modal.get()};
msgBox->setModal(true);
msgBox->buttonClicked().connect([=] (Wt::StandardButton btn)
auto* delBtn {modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"))};
delBtn->clicked().connect([=]
{
if (btn == Wt::StandardButton::Yes)
{
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
User::pointer user {User::find(LmsApp->getDbSession(), userId)};
if (user)
user.remove();
User::pointer user {User::find(LmsApp->getDbSession(), userId)};
if (user)
user.remove();
_container->removeWidget(entry);
}
else
{
delBtn->removeChild(msgBox);
}
_container->removeWidget(entry);
LmsApp->getModalManager().dispose(modalPtr);
});
msgBox->show();
auto* cancelBtn {modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel"))};
cancelBtn->clicked().connect([=]
{
LmsApp->getModalManager().dispose(modalPtr);
});
LmsApp->getModalManager().show(std::move(modal));
});
}
}
+5 -8
View File
@@ -168,10 +168,11 @@ Release::refreshView()
}
{
Wt::WPushButton* moreBtn {bindNew<Wt::WPushButton>("more-btn", Wt::WString::tr("Lms.Explore.template.more-btn"), Wt::TextFormat::XHTML)};
moreBtn->clicked().connect([=]
Wt::WPushButton* playShuffled {bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)};
playShuffled->setDefault(false);
playShuffled->clicked().connect([=]
{
displayReleasePopupMenu(*moreBtn, *releaseId, releasesAction);
releasesAction.emit(PlayQueueAction::PlayShuffled, {*releaseId});
});
}
@@ -351,11 +352,7 @@ Release::refreshLinks(const Database::Release::pointer& release)
if (mbid)
{
setCondition("if-has-mbid", true);
Wt::WLink link {"https://musicbrainz.org/release/" + std::string {mbid->getAsString()}};
link.setTarget(Wt::LinkTarget::NewWindow);
bindNew<Wt::WAnchor>("mbid-link", link, Wt::WString::tr("Lms.Explore.musicbrainz-release"));
bindString("mbid-link", std::string {"https://musicbrainz.org/release/"} + std::string {mbid->getAsString()});
}
}
+20 -15
View File
@@ -41,25 +41,30 @@ Releases::Releases(Filters& filters)
{
addFunction("tr", &Wt::WTemplate::Functions::tr);
auto bindMenuItem {[this](const std::string& var, const Wt::WString& title, ReleaseCollector::Mode mode)
{
auto* menu {bindNew<Wt::WMenu>("mode")};
auto addItem = [this](Wt::WMenu& menu,const Wt::WString& str, ReleaseCollector::Mode mode)
auto *menuItem {bindNew<Wt::WPushButton>(var, title)};
menuItem->clicked().connect([=]
{
auto* item {menu.addItem(str)};
item->clicked().connect([this, mode] { refreshView(mode); });
refreshView(mode);
_currentActiveItem->removeStyleClass("active");
menuItem->addStyleClass("active");
_currentActiveItem = menuItem;
});
if (mode == _defaultMode)
item->renderSelected(true);
};
if (mode == _defaultMode)
{
_currentActiveItem = menuItem;
_currentActiveItem->addStyleClass("active");
}
}};
addItem(*menu, Wt::WString::tr("Lms.Explore.random"), ReleaseCollector::Mode::Random);
addItem(*menu, Wt::WString::tr("Lms.Explore.starred"), ReleaseCollector::Mode::Starred);
addItem(*menu, Wt::WString::tr("Lms.Explore.recently-played"), ReleaseCollector::Mode::RecentlyPlayed);
addItem(*menu, Wt::WString::tr("Lms.Explore.most-played"), ReleaseCollector::Mode::MostPlayed);
addItem(*menu, Wt::WString::tr("Lms.Explore.recently-added"), ReleaseCollector::Mode::RecentlyAdded);
addItem(*menu, Wt::WString::tr("Lms.Explore.all"), ReleaseCollector::Mode::All);
}
bindMenuItem("random", Wt::WString::tr("Lms.Explore.random"), ReleaseCollector::Mode::Random);
bindMenuItem("starred", Wt::WString::tr("Lms.Explore.starred"), ReleaseCollector::Mode::Starred);
bindMenuItem("recently-played", Wt::WString::tr("Lms.Explore.recently-played"), ReleaseCollector::Mode::RecentlyPlayed);
bindMenuItem("most-played", Wt::WString::tr("Lms.Explore.most-played"), ReleaseCollector::Mode::MostPlayed);
bindMenuItem("recently-added", Wt::WString::tr("Lms.Explore.recently-added"), ReleaseCollector::Mode::RecentlyAdded);
bindMenuItem("all", Wt::WString::tr("Lms.Explore.all"), ReleaseCollector::Mode::All);
Wt::WPushButton* playBtn {bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.template.play-btn"), Wt::TextFormat::XHTML)};
playBtn->clicked().connect([this]
+1
View File
@@ -51,6 +51,7 @@ namespace UserInterface
static constexpr std::size_t _batchSize {_maxItemsPerLine};
static constexpr std::size_t _maxCount {_maxItemsPerLine * 32};
Wt::WWidget* _currentActiveItem {};
InfiniteScrollingContainer* _container {};
ReleaseCollector _releaseCollector;
static constexpr ReleaseCollector::Mode _defaultMode {ReleaseCollector::Mode::Random};