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
+7 -7
View File
@@ -10,7 +10,7 @@
<label class="form-label" for="${id:media-directory}">
${tr:Lms.Admin.Database.path}
</label>
${media-directory}
${media-directory class="form-control"}
<div class="invalid-feedback">
${media-directory-info}
</div>
@@ -19,7 +19,7 @@
<label class="form-label" for="${id:update-period}">
${tr:Lms.Admin.Database.update-period}
</label>
${update-period}
${update-period class="form-control"}
<div class="invalid-feedback">
${update-period-info"}
</div>
@@ -28,7 +28,7 @@
<label class="form-label" for="${id:update-start-time}">
${tr:Lms.Admin.Database.update-start-time}
</label>
${update-start-time}
${update-start-time class="form-control"}
<div class="invalid-feedback">
${update-start-time-info}
</div>
@@ -38,22 +38,22 @@
<label class="form-label" for="${id:recommendation-engine-type}">
${tr:Lms.Admin.Database.recommendation-engine-type}
</label>
${recommendation-engine-type}
${recommendation-engine-type class="form-control"}
<div class="invalid-feedback">
${recommendation-engine-type-info}
</div>
</div>
<div class="col-12">
<label class="form-label" for="${id:tags}">
<label class="form-label" for="${id:tags}">
${tr:Lms.Admin.Database.tags}
</label>
${tags}
${tags class="form-control"}
<div class="invalid-feedback">
${tags-info}
</div>
</div>
<div class="col-12">
${apply-btn class="btn-primary me-1"}${discard-btn}
${apply-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"}
</div>
</form>
<br/>
+3 -3
View File
@@ -17,7 +17,7 @@
<label class="form-label" for="${id:login}">
${tr:Lms.login}
</label>
${login}
${login class="form-control"}
<div class="invalid-feedback">
${login-info}
</div>
@@ -28,7 +28,7 @@
<label class="form-label" for="${id:password}">
${tr:Lms.password-new}
</label>
${password}
${password class="form-control"}
<div class="invalid-feedback">
${password-info}
</div>
@@ -48,7 +48,7 @@
</div>
${</if-demo>}
<div class="col-12">
${save-btn class="btn-primary"}
${save-btn class="btn btn-primary"}
</div>
</form>
</message>
+22 -3
View File
@@ -5,17 +5,36 @@
<h2>${tr:Lms.Admin.Users.users}</h2>
${users}
${<if-can-create-user>}
${add-btn class="btn-primary Lms-admin-users-add-btn"}
${add-btn class="btn btn-primary"}
${</if-can-create-user>}
</message>
<message id="Lms.Admin.Users.template.entry">
<div class="row align-items-center mb-3">
<div class="col-auto me-auto">
${name}${<if-tag>} <span class="badge bg-info">${tag}</span>${</if-tag>}
${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-warning btn-sm"} ${del-btn class="btn-danger btn-sm"}${</if-edit>}
${<if-edit>}${edit-btn class="btn btn-sm btn-warning me-1"} ${del-btn class="btn btn-sm btn-danger"}${</if-edit>}
</div>
</div>
</message>
<message id="Lms.Admin.Users.template.delete-user">
<div class="modal fade" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">${tr:Lms.Admin.Users.del-user}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Modal body text goes here.</p>
</div>
<div class="modal-footer">
${del-btn class="btn btn-danger me-1"}${cancel-btn class="btn btn-secondary"}
</div>
</div>
</div>
</div>
</message>
+1 -1
View File
@@ -4,7 +4,7 @@
<message id="Lms.Explore.Artist.template">
<div class="mb-3">
${name}
<h3>${name}</h3>
${clusters class="mb-2"}
${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"}
</div>
+2 -2
View File
@@ -9,7 +9,7 @@
<message id="Lms.Explore.template.play-btn"><i class="fa fa-fw fa-play"></i></message>
<message id="Lms.Explore.template.filters">
${add-filter class="btn-sm btn-outline-primary me-2"}${clusters class="d-inline-block align-items-center"}
${add-filter class="btn btn-sm btn-outline-primary me-2"}${clusters class="d-inline-block align-items-center"}
</message>
<message id="Lms.Explore.template.add-filter">
@@ -28,7 +28,7 @@
</div>
<div class="col-12">
<div class="col-sm-offset-2 col-sm-10">
${add-btn class="btn-primary me-1"}${cancel-btn}
${add-btn class="btn btn-primary me-1"}${cancel-btn class="btn btn-secondary"}
</div>
</div>
</form>
+3 -3
View File
@@ -11,7 +11,7 @@
<label class="form-label" for="${id:login-name}">
${tr:Lms.login}
</label>
${login-name}
${login-name class="form-control"}
<div class="invalid-feedback">
${login-name-info}
</div>
@@ -20,7 +20,7 @@
<label class="form-label" for="${id:password}">
${tr:Lms.password}
</label>
${password}
${password class="form-control"}
<div class="invalid-feedback">
${password-info}
</div>
@@ -34,7 +34,7 @@
</div>
</div>
<div class="col-12">
${login-btn class="btn-primary"}
${login-btn class="btn btn-primary"}
</div>
</div>
</form>
+2 -1
View File
@@ -5,7 +5,8 @@
${navbar}
<div class="container">
${contents}
${notifications class="toast-container position-fixed top-0 end-0 p-3"}
${notifications class="toast-container Lms-notification-container p-3"}
${modal}
</div>
${player class="fixed-bottom bg-dark Lms-player"}
</message>
-1
View File
@@ -117,7 +117,6 @@
<message id="Lms.Explore.download">Download</message>
<message id="Lms.Explore.filter-added">Filter added</message>
<message id="Lms.Explore.filters">Filters</message>
<message id="Lms.Explore.links">Links</message>
<message id="Lms.Explore.most-played">Most played</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
-1
View File
@@ -117,7 +117,6 @@
<message id="Lms.Explore.download">Télécharger</message>
<message id="Lms.Explore.filter-added">Filtre ajouté</message>
<message id="Lms.Explore.filters">Filtres</message>
<message id="Lms.Explore.links">Liens</message>
<message id="Lms.Explore.most-played">Plus joués</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz Artist</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz Release</message>
-1
View File
@@ -116,7 +116,6 @@
<message id="Lms.Explore.download">Download</message>
<message id="Lms.Explore.filter-added">Filtro aggiunto</message>
<message id="Lms.Explore.filters">Filtri</message>
<message id="Lms.Explore.links">Collegamenti</message>
<message id="Lms.Explore.most-played">Più ascoltati</message>
<message id="Lms.Explore.musicbrainz-artist">Artista MusicBrainz</message>
<message id="Lms.Explore.musicbrainz-release">Album MusicBrainz</message>
-1
View File
@@ -117,7 +117,6 @@
<message id="Lms.Explore.download">下载</message>
<message id="Lms.Explore.filter-added">筛选已添加</message>
<message id="Lms.Explore.filters">筛选</message>
<message id="Lms.Explore.links">链接</message>
<message id="Lms.Explore.most-played">播放最多</message>
<message id="Lms.Explore.musicbrainz-artist">MusicBrainz 歌手</message>
<message id="Lms.Explore.musicbrainz-release">MusicBrainz 专辑</message>
+11 -8
View File
@@ -16,11 +16,20 @@
${<if-has-release-artists>}${artists}${</if-has-release-artists>}
${<if-has-various-release-artists>}${tr:Lms.Explore.various-artists}${</if-has-various-release-artists>}
<div class="small text-muted">${<if-has-year>}${year}${<if-has-orig-year>} (${orig-year})${</if-has-orig-year>} · ${</if-has-year>}${duration}</div>
${clusters}
${clusters class="mb-2"}
</div>
<div class="col-12">
<div class="mb-3">
${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"}
${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">
<li>${play-shuffled 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>}
</ul>
</span>
</div>
${container class="d-grid gap-3"}
</div>
@@ -38,12 +47,6 @@
${copyright class="text-muted"}
</div>
${</if-has-copyright>}
${<if-has-mbid>}
<div class="col-12 col-md-4">
<h3>${tr:Lms.Explore.links}</h3>
${mbid-link class="text-muted"}
</div>
${</if-has-mbid>}
</div>
</message>
+20 -1
View File
@@ -4,7 +4,26 @@
<message id="Lms.Explore.Releases.template">
<div class="mb-3">
${mode class="nav nav-pills mb-2"}
<ul class="nav nav-pills mb-2">
<li class="nav-item">
${random class="nav-link"}
</li>
<li class="nav-item">
${starred class="nav-link"}
</li>
<li class="nav-item">
${recently-played class="nav-link"}
</li>
<li class="nav-item">
${most-played class="nav-link"}
</li>
<li class="nav-item">
${recently-added class="nav-link"}
</li>
<li class="nav-item">
${all class="nav-link"}
</li>
</ul>
${play-btn class="btn btn-outline-primary me-2"}${more-btn class="btn btn-outline-primary"}
</div>
${releases}
+20 -20
View File
@@ -13,7 +13,7 @@
<label class="form-label" for="${id:transcode-mode}">
${tr:Lms.Settings.transcode-mode}
</label>
${transcode-mode}
${transcode-mode class="form-control"}
<div class="invalid-feedback">
${transcode-mode-info}
</div>
@@ -22,7 +22,7 @@
<label class="form-label" for="${id:transcode-format}">
${tr:Lms.Settings.transcode-format}
</label>
${transcode-format}
${transcode-format class="form-control"}
<div class="invalid-feedback">
${transcode-format-info}
</div>
@@ -32,7 +32,7 @@
${tr:Lms.Settings.transcode-bitrate}
</label>
<div class="input-group mb-3">
${transcode-bitrate}
${transcode-bitrate class="form-control"}
<span class="input-group-text">kbps</span>
</div>
<div class="invalid-feedback">
@@ -43,7 +43,7 @@
<label class="form-label" for="${id:replaygain-mode}">
${tr:Lms.Settings.replaygain-mode}
</label>
${replaygain-mode}
${replaygain-mode class="form-control"}
<div class="invalid-feedback">
${replaygain-mode-info}
</div>
@@ -53,7 +53,7 @@
${tr:Lms.Settings.replaygain-preamp}
</label>
<div class="input-group mb-3">
${replaygain-preamp}
${replaygain-preamp class="form-control"}
<span class="input-group-text">dB</span>
</div>
<div class="invalid-feedback">
@@ -61,11 +61,11 @@
</div>
</div>
<div class="col-lg-6">
<label class="form-label" for="${id:replaygain-preamp-no-rg-info}">
<label class="form-label" for="${id:replaygain-preamp-no-rg-info}">
${tr:Lms.Settings.replaygain-preamp-no-rg-info}
</label>
<div class="input-group mb-3">
${replaygain-preamp-no-rg-info}
${replaygain-preamp-no-rg-info class="form-control"}
<span class="input-group-text">dB</span>
</div>
<div class="invalid-feedback">
@@ -78,7 +78,7 @@
<div class="form-check">
${subsonic-transcode-enable class="form-check-input"}
<label class="form-check-label" for="${id:subsonic-transcode-enable}">
${tr:Lms.Settings.transcode-enable}
${tr:Lms.Settings.transcode-enable class="form-control"}
</label>
<div class="invalid-feedback">
${subsonic-transcode-enable-info}
@@ -86,20 +86,20 @@
</div>
</div>
<div class="col-lg-6">
<label class="form-label" for="${id:subsonic-transcode-format}">
<label class="form-label" for="${id:subsonic-transcode-format}">
${tr:Lms.Settings.transcode-format}
</label>
${subsonic-transcode-format}
${subsonic-transcode-format class="form-control"}
<div class="invalid-feedback">
${subsonic-transcode-format-info class="help-block"}
</div>
</div>
<div class="col-lg-6">
<label class="col-lg-3 form-label" for="${id:subsonic-transcode-bitrate}">
<label class="col-lg-3 form-label" for="${id:subsonic-transcode-bitrate}">
${tr:Lms.Settings.transcode-bitrate}
</label>
<div class="input-group mb-3">
${subsonic-transcode-bitrate}
${subsonic-transcode-bitrate class="form-control"}
<span class="input-group-text">kbps</span>
</div>
<div class="invalid-feedback">
@@ -107,10 +107,10 @@
</div>
</div>
<div class="col-12">
<label class="form-label" for="${id:subsonic-artist-list-mode}">
<label class="form-label" for="${id:subsonic-artist-list-mode}">
${tr:Lms.Settings.subsonic-artist-list-mode}
</label>
${subsonic-artist-list-mode}
${subsonic-artist-list-mode class="form-control"}
<div class="invalid-feedback">
${subsonic-artist-list-mode-info class="help-block"}
</div>
@@ -121,7 +121,7 @@
<label class="form-label" for="${id:scrobbler}">
${tr:Lms.Settings.scrobbling.scrobbler}
</label>
${scrobbler}
${scrobbler class="form-control"}
<div class="invalid-feedback">
${scrobbler-info class="help-block"}
</div>
@@ -130,7 +130,7 @@
<label class="form-label" for="${id:listenbrainz-token}">
${tr:Lms.Settings.scrobbling.listenbrainz-token}
</label>
${listenbrainz-token}
${listenbrainz-token class="form-control"}
<div class="invalid-feedback">
${listenbrainz-token-info class="help-block"}
</div>
@@ -142,7 +142,7 @@
<label class="form-label" for="${id:password-old}">
${tr:Lms.password-old}
</label>
${password-old}
${password-old class="form-control"}
<div class="invalid-feedback">
${password-old-info class="help-block"}
</div>
@@ -152,7 +152,7 @@
<label class="col-lg-3 control-label" for="${id:password}">
${tr:Lms.password-new}
</label>
${password}
${password class="form-control"}
<div class="invalid-feedback">
${password-info class="help-block"}
</div>
@@ -161,14 +161,14 @@
<label class="col-lg-3 control-label" for="${id:password-confirm}">
${tr:Lms.password-confirm}
</label>
${password-confirm}
${password-confirm class="form-control"}
<div class="invalid-feedback">
${password-confirm-info class="help-block"}
</div>
</div>
${</if-has-change-password>}
<div class="col-12">
${apply-btn class="btn-primary me-1"}${discard-btn}
${apply-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"}
</div>
</form>
</message>
+6
View File
@@ -12,6 +12,12 @@ body {
padding-bottom: 78px;
}
.Lms-notification-container {
position: fixed;
bottom: 78px;
right: 0px;
}
.Lms-player {
height: 70px;
}
+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};