|
#include "ThemeToggleButton.hpp"
|
|
|
|
#include <Wt/WPushButton.h>
|
|
#include <Wt/WPopupMenu.h>
|
|
#include <Wt/WMenuItem.h>
|
|
|
|
#include <string>
|
|
#include <Wt/WString.h>
|
|
|
|
#include <Wt/WIcon.h>
|
|
|
|
|
|
namespace
|
|
{
|
|
Wt::WString autoIconString;
|
|
Wt::WString ItemStringAuto;
|
|
|
|
Wt::WString lightIconString;
|
|
Wt::WString ItemStringLight;
|
|
|
|
Wt::WString darkIconString;
|
|
Wt::WString ItemStringDark;
|
|
|
|
Wt::WString MenuButtonString;
|
|
|
|
void setupItemStrings()
|
|
{
|
|
auto tempIconString = Wt::WString::tr("ThemeToggle.Auto.Icon");
|
|
autoIconString =
|
|
tempIconString == "??ThemeToggle.Auto.Icon??" ?
|
|
Wt::WString("<i class=\"bi bi-circle-half\"></i> ")
|
|
: tempIconString;
|
|
|
|
auto tempItemString = Wt::WString::tr("ThemeToggle.Auto");
|
|
|
|
ItemStringAuto =
|
|
Wt::WString("{1}")
|
|
.arg(
|
|
tempItemString == "??ThemeToggle.Auto??" ?
|
|
"Auto"
|
|
: tempItemString
|
|
);
|
|
|
|
|
|
tempIconString = Wt::WString::tr("ThemeToggle.Light.Icon");
|
|
lightIconString =
|
|
tempIconString == "??ThemeToggle.Light.Icon??" ?
|
|
Wt::WString("<i class=\"bi bi-sun-fill\"></i> ")
|
|
: tempIconString;
|
|
|
|
tempItemString = Wt::WString::tr("ThemeToggle.Light");
|
|
ItemStringLight =
|
|
Wt::WString("{1}")
|
|
.arg(
|
|
tempItemString == "??ThemeToggle.Light??" ?
|
|
"Light"
|
|
: tempItemString
|
|
);
|
|
|
|
tempIconString = Wt::WString::tr("ThemeToggle.Dark.Icon");
|
|
darkIconString =
|
|
tempIconString == "??ThemeToggle.Dark.Icon??" ?
|
|
Wt::WString("<i class=\"bi bi-moon-stars-fill\"></i> ")
|
|
: tempIconString;
|
|
|
|
tempItemString = Wt::WString::tr("ThemeToggle.Dark");
|
|
ItemStringDark =
|
|
Wt::WString("{1}")
|
|
.arg(
|
|
tempItemString == "??ThemeToggle.Dark??" ?
|
|
"Dark"
|
|
: tempItemString
|
|
);
|
|
|
|
tempItemString = Wt::WString::tr("ThemeToggle.Menu");
|
|
MenuButtonString =
|
|
Wt::WString("{2}{1}")
|
|
.arg(
|
|
tempItemString == "??ThemeToggle.Menu??" ?
|
|
"Toggle theme"
|
|
: tempItemString
|
|
);
|
|
}
|
|
|
|
|
|
|
|
std::string jsPrepared = R"JSDelim(
|
|
|
|
/*!
|
|
* Based on:
|
|
*
|
|
* Color mode toggler for Bootstrap's docs (https://getbootstrap.com/)
|
|
* Copyright 2011-2025 The Bootstrap Authors
|
|
* Licensed under the Creative Commons Attribution 3.0 Unported License.
|
|
*
|
|
* Removed pure js setup, enabled cooperation with Wt
|
|
*/
|
|
|
|
window.getStoredTheme = function(){ return localStorage.getItem('theme'); }
|
|
window.setStoredTheme = function(theme){ localStorage.setItem('theme', theme); }
|
|
|
|
window.getPreferredTheme = function(){
|
|
const storedTheme = getStoredTheme()
|
|
if (storedTheme) {
|
|
return storedTheme
|
|
}
|
|
|
|
return window.matchMedia('(prefers-color-scheme: dark)')
|
|
.matches ? 'dark' : 'light'
|
|
}
|
|
|
|
window.setTheme = function(theme)
|
|
{
|
|
// if (theme === 'auto') { // pure BS 5.3 way
|
|
// document.documentElement.setAttribute(
|
|
// 'data-bs-theme',
|
|
// (
|
|
// window.matchMedia('(prefers-color-scheme: dark)')
|
|
// .matches ? 'dark' : 'light'
|
|
// )
|
|
// )
|
|
// } else {
|
|
// document.documentElement.setAttribute('data-bs-theme', theme)
|
|
// }
|
|
|
|
// Mixed Bootstrap 5.2.3 & 5.3 way
|
|
// Remove existing theme classes (if any, like 'light' or 'dark' from an older setup)
|
|
document.documentElement.classList.remove('light', 'dark');
|
|
document.body.classList.remove('light', 'dark'); // Often applied to body as well
|
|
|
|
if (theme === 'auto') {
|
|
const preferred = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
document.documentElement.setAttribute('data-bs-theme', preferred); // You can keep this for future 5.3+ upgrade
|
|
document.body.classList.add(preferred); // Add the class for 5.2.3
|
|
} else {
|
|
document.documentElement.setAttribute('data-bs-theme', theme); // Keep this for future 5.3+ upgrade
|
|
document.body.classList.add(theme); // Add the class for 5.2.3
|
|
}
|
|
}
|
|
|
|
// console.log("Theme functions defined."); // debug logs
|
|
|
|
|
|
// Initial theme setting
|
|
setTheme(getPreferredTheme());
|
|
|
|
// Media query listener
|
|
const mediaQueryList = window.matchMedia('(prefers-color-scheme: dark)');
|
|
mediaQueryList.addEventListener('change', (e) => {
|
|
const storedTheme = getStoredTheme();
|
|
if (storedTheme !== 'light' && storedTheme !== 'dark') {
|
|
setTheme(getPreferredTheme());
|
|
}
|
|
});
|
|
|
|
(() => {
|
|
// fire JSignal
|
|
{1} // gets replaced with JSignal call
|
|
|
|
// console.log("Theme Notified on Load"); // debug logs
|
|
|
|
|
|
})(); // run function on load
|
|
|
|
|
|
// console.log("Script execution finished (outside listener)."); // debug logs
|
|
|
|
|
|
)JSDelim";
|
|
|
|
const std::string slotJS = R"JSDelim(
|
|
|
|
function(obj, event, theme){
|
|
// console.log("Theme Selection:", theme); // debug logs
|
|
setStoredTheme(theme)
|
|
setTheme(theme)
|
|
}
|
|
|
|
)JSDelim";
|
|
}
|
|
|
|
|
|
namespace Bootstrap5
|
|
{
|
|
|
|
ThemeToggleButton::ThemeToggleButton()
|
|
: selected(this, "notifyTheme"), themeSelect(slotJS, 1, this)
|
|
{
|
|
setupItemStrings();
|
|
prepareContent();
|
|
setupConnections();
|
|
|
|
doJavaScript(Wt::WString(jsPrepared).arg(selected.createCall({"getPreferredTheme()"})).toUTF8());
|
|
}
|
|
|
|
void ThemeToggleButton::prepareContent()
|
|
{
|
|
setTextFormat(Wt::TextFormat::XHTML);
|
|
setText(Wt::WString(MenuButtonString).arg(autoIconString)); // sets Text with auto Icon
|
|
{
|
|
auto uPop = std::make_unique<Wt::WPopupMenu>();
|
|
popMenu = uPop.get();
|
|
setMenu(std::move(uPop));
|
|
}
|
|
{
|
|
auto uItem = std::make_unique<Wt::WMenuItem>(ItemStringLight);
|
|
|
|
auto iconText = std::make_unique<Wt::WText>(lightIconString);
|
|
iconText->setTextFormat(Wt::TextFormat::XHTML);
|
|
iconText->addStyleClass("me-2 opacity-50");
|
|
uItem->anchor()->insertWidget(0, std::move(iconText));
|
|
|
|
uItem->triggered().connect([this](){
|
|
themeSelect.exec(this->jsRef(), "null", "\""+ Theme::Light +"\"");
|
|
this->setText(Wt::WString(MenuButtonString).arg(lightIconString));
|
|
s_themeChanged(Theme::Light);
|
|
});
|
|
|
|
popMenu->addItem(std::move(uItem));
|
|
}
|
|
{
|
|
auto uItem = std::make_unique<Wt::WMenuItem>(ItemStringDark);
|
|
|
|
auto iconText = std::make_unique<Wt::WText>(darkIconString);
|
|
iconText->setTextFormat(Wt::TextFormat::XHTML);
|
|
iconText->addStyleClass("me-2 opacity-50");
|
|
uItem->anchor()->insertWidget(0, std::move(iconText));
|
|
|
|
uItem->triggered().connect([this](){
|
|
themeSelect.exec(this->jsRef(), "null", "\""+ Theme::Dark +"\"");
|
|
this->setText(Wt::WString(MenuButtonString).arg(darkIconString));
|
|
s_themeChanged(Theme::Dark);
|
|
});
|
|
|
|
popMenu->addItem(std::move(uItem));
|
|
}
|
|
{
|
|
auto uItem = std::make_unique<Wt::WMenuItem>(ItemStringAuto);
|
|
|
|
auto iconText = std::make_unique<Wt::WText>(autoIconString);
|
|
iconText->setTextFormat(Wt::TextFormat::XHTML);
|
|
iconText->addStyleClass("me-2 opacity-50");
|
|
uItem->anchor()->insertWidget(0, std::move(iconText));
|
|
|
|
uItem->triggered().connect([this](){
|
|
themeSelect.exec(this->jsRef(), "null", "\""+ Theme::Auto +"\"");
|
|
this->setText(Wt::WString(MenuButtonString).arg(autoIconString));
|
|
s_themeChanged(Theme::Auto);
|
|
});
|
|
|
|
popMenu->addItem(std::move(uItem));
|
|
}
|
|
}
|
|
|
|
void ThemeToggleButton::setupConnections()
|
|
{
|
|
selected.connect([this](std::string ts){
|
|
// should only get called in the beginning to set current theme from localStorage
|
|
if(Theme::Light == ts)
|
|
{
|
|
setText(Wt::WString(MenuButtonString).arg(lightIconString));
|
|
s_themeChanged(Theme::Light);
|
|
Wt::log("debug") << "Bootstrap5::Theme" << "::changed to Theme::Light, aka: " << Theme::Light;
|
|
}
|
|
else if(Theme::Dark == ts)
|
|
{
|
|
setText(Wt::WString(MenuButtonString).arg(darkIconString));
|
|
s_themeChanged(Theme::Dark);
|
|
Wt::log("debug") << "Bootstrap5::Theme" << "::changed to Theme::Dark, aka: " << Theme::Dark;
|
|
}
|
|
else // Auto
|
|
{
|
|
setText(Wt::WString(MenuButtonString).arg(autoIconString));
|
|
s_themeChanged(Theme::Auto);
|
|
Wt::log("debug") << "Bootstrap5::Theme" << "::changed to Theme::Auto, aka: " << Theme::Auto;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
} // end namespace Bootstrap5
|