#include "ThemeToggleButton.hpp" #include #include #include #include #include #include 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(" ") : 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(" ") : 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(" ") : 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(); popMenu = uPop.get(); setMenu(std::move(uPop)); } { auto uItem = std::make_unique(ItemStringLight); auto iconText = std::make_unique(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(ItemStringDark); auto iconText = std::make_unique(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(ItemStringAuto); auto iconText = std::make_unique(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