I’ve altered ThemeChangerAIO to work offline, so that no ressources need to be downloaded.
I did not change a lot but hard coding the theme names and downloading them into assets/dbc_bootstrap_themes
with this script:
import requests
import os
import dash_bootstrap_components as dbc
dbc_themes_url = {
item: getattr(dbc.themes, item)
for item in dir(dbc.themes)
if not item.startswith(("_", "GRID"))
}
for theme, url in dbc_themes_url.items():
response = requests.get(url)
with open(os.path.join(f'{theme}.css'), 'w') as f:
f.write(response.text)
I needed
@app.server.before_request
def before_request():
if '.css' in request.path and "dbc_bootstrap_themes" in request.path:
response = make_response(request.get_data())
response.headers["Content-Type"] = "text/css"
return response
because otherwise the offline served themes were of MIME type text/html
even though they are appended as text/css
in the clientside_callback
Here’s the full code of what I tried so far. The stylesheet is correctly appended as I can see, but the theme is not applied.
from dash import Dash, html, dcc, clientside_callback, Output, Input
from dataclasses import dataclass
import os
from flask import request, make_response
import dash_bootstrap_components as dbc
app = Dash(__name__)
@app.server.before_request
def before_request():
if '.css' in request.path and "dbc_bootstrap_themes" in request.path:
response = make_response(request.get_data())
response.headers["Content-Type"] = "text/css"
return response
@dataclass(frozen=True)
class ThemeChanger:
element_id: str = "theme-changer"
dropdown_id: str = "theme-changer-dropdown"
default_theme: str = "SANDSTONE"
dbc_boostrap_themes = [
"BOOTSTRAP",
"CERULEAN",
"COSMO",
"CYBORG",
"DARKLY",
"FLATLY",
"JOURNAL",
"LITERA",
"LUMEN",
"LUX",
"MATERIA",
"MINTY",
"MORPH",
"PULSE",
"QUARTZ",
"SANDSTONE",
"SIMPLEX",
"SKETCHY",
"SLATE",
"SOLAR",
"SPACELAB",
"SUPERHERO",
"UNITED",
"VAPOR",
"YETI",
"ZEPHYR",
]
def get_dropdown_probs(self) -> dict[str, list[dict[str, str]]]:
return {
"value": os.path.join(
os.getcwd(),
"assets",
"dbc_bootstrap_themes",
f"{self.default_theme}.css",
),
"options": [
{
"label": theme,
"value": os.path.join(
os.getcwd(),
"assets",
"dbc_bootstrap_themes",
f"{theme}.css",
),
}
for theme in self.dbc_boostrap_themes
],
}
def gen_component(self) -> html.Div:
dropdown_probs: dict = self.get_dropdown_probs()
return html.Div(
id=self.element_id,
style={"display": "inline"},
children=[
dcc.Dropdown(id=self.dropdown_id, clearable=False, **dropdown_probs),
],
)
def __post_init__(self):
clientside_callback(
"""
function switcher(path) {
var stylesheets = document.querySelectorAll(
`link[rel=stylesheet][href*="dbc_bootstrap_themes"]`
);
console.log(stylesheets);
// The delay in updating the stylesheet reduces the flash when changing themes
stylesheets[stylesheets.length - 1].href = path
setTimeout(function() {
for (let i = 0; i < stylesheets.length -1; i++) {
stylesheets[i].href = path;
}
}, 500);
}
""",
Output(self.element_id, "key"),
Input(self.dropdown_id, "value"),
)
clientside_callback(
"""
function(path) {
// initialize theme
var link = document.createElement("link");
link.type = "text/css";
link.rel = "stylesheet";
link.href = path;
console.log(link);
document.head.appendChild(link);
}
""",
Output(self.element_id, "role"),
Input(self.dropdown_id, "value"),
)
theme_changer = ThemeChanger()
tabs = dbc.Tabs([
dbc.Tab("Hello"),
dbc.Tab("World")
]
)
app.layout = html.Div([
theme_changer.gen_component(),
tabs
])
if __name__ == '__main__':
app.run_server(debug=True)
Maybe @AnnMarieW could have a quick look over it?