Still converting to bootstrap 5. Moved away from Wt's boostrap5 theme to better stick to vanilla bootstrap
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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; }
|
||||
};
|
||||
}
|
||||
@@ -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");
|
||||
|
||||
@@ -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 {};
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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"},
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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};
|
||||
|
||||
Reference in New Issue
Block a user