Closed vnijs closed 1 year ago
Yes, but it'll be tricky as the current shinyswatch themes are only html dependencies.
It will at least need a page refresh. I don't know off the top of my head if the ui
can be a function in py-shiny. If so, the we can make it work. Not pretty, but it'd work.
Currently py-shiny does not have theme support built in like R shiny
Thanks for the quick comment @schloerke. Page refresh isn't a big deal. Would that involve something like onclick = "window.location.reload();"
?
Not sure what you mean by "if the ui can be a function in py-shiny". In the below app_ui
is a function. I started on the below before realizing I didn't know a way to push the theme into the header. I also use functions a lot to reuse pieces of ui across apps.
from shiny import App, Inputs, Outputs, Session, ui, reactive
import shinyswatch
def app_ui():
return ui.page_fluid(
# Theme code - start
shinyswatch.theme.sketchy(),
# Theme code - end
ui.input_select(
id="select_theme",
label="Select a theme:",
selected="superhero",
choices={
"superhero": "Super Hero",
"darkly": "Darkly",
"sketchy": "Sketchy",
},
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.select_theme, ignore_none=True)
def set_theme():
return shinyswatch.get_theme(f"{input.select_theme()}")
app = App(app_ui(), server)
Oh! Great!
Cliff notes before a proper approach on Monday...
Gave it a try but it keeps resetting to the original value.
from shiny import App, Inputs, Outputs, Session, ui, reactive
import shinyswatch
if "theme" not in globals():
theme = {"theme": "superhero"}
def app_ui():
return ui.page_fluid(
shinyswatch.get_theme(theme.get("theme", "superhero")),
ui.tags.script(
"""
Shiny.addCustomMessageHandler('refresh', function(message) {
window.location.reload();
});
"""
),
ui.input_select(
id="select_theme",
label="Select a theme:",
selected=theme.get("theme", "superhero"),
choices=["superhero", "darkly", "sketchy"],
),
)
def server(input: Inputs, output: Outputs, session: Session):
@reactive.Effect
@reactive.event(input.select_theme, ignore_none=True)
async def set_theme():
if input.select_theme() != theme.get("theme", "superhero"):
theme["theme"] = input.select_theme()
await session.send_custom_message("refresh", "")
app = App(app_ui(), server)
app_ui
isn't called on browser restart.
from shiny import App, Inputs, Outputs, Session, ui, reactive, render
import shinyswatch
if "theme" not in globals():
print("Define theme dictionary. Was not in globals()")
theme = {"theme": "superhero"}
def app_ui():
print("ui function was called")
return ui.page_fluid(
shinyswatch.get_theme(theme.get("theme", "superhero")),
ui.tags.script(
"""
Shiny.addCustomMessageHandler('refresh', function(message) {
window.location.reload();
});
"""
),
# ui.input_select(
# id="select_theme",
# label="Select a theme:",
# selected=theme.get("theme", "superhero"),
# choices=["superhero", "darkly", "sketchy"],
# ),
ui.output_ui("ui_select_theme"),
)
def server(input: Inputs, output: Outputs, session: Session):
print("server function was called")
print(theme.get("theme", "superhero"))
@reactive.Effect
@reactive.event(input.select_theme, ignore_none=True)
async def set_theme():
if input.select_theme() != theme.get("theme", "superhero"):
theme["theme"] = input.select_theme()
await session.send_custom_message("refresh", "")
# ui.update_select("select_theme", selected=theme.get("theme", "superhero"))
@output(id="ui_select_theme")
@render.ui
def ui_select_theme():
return (
ui.input_select(
id="select_theme",
label="Select a theme:",
selected=theme.get("theme", "superhero"),
choices=["superhero", "darkly", "sketchy"],
),
)
app = App(app_ui(), server, debug=False)
app_ui
isn't called on browser restart
That's not good. Not much point of having a function, then. 😝 This behavior is definitely a bug in py-shiny.
I thought your solution would have worked (but only glancing through it). But if the ui isn't being recalculated of refresh, then we're already off to a rough start.
Thanks @schloerke. Report as an issue to py-shiny?
If you're up for it! Please reference this one. Thank you!
I missed that app_ui
was being called as it was being sent into App
. Instead, app_ui
should be a function that takes a starlette.requests.Request
object for context about the request.
Updated app below:
import shinyswatch
from htmltools import Tag
from starlette.requests import Request as StarletteRequest
from shiny import App, Inputs, Outputs, Session, reactive, ui
if "theme_obj" not in globals():
print("Define theme dictionary. Was not in globals()")
theme_obj = {"theme": "superhero"}
def app_ui(request: StarletteRequest) -> Tag:
print("ui function was called")
return ui.page_fluid(
shinyswatch.get_theme(theme_obj.get("theme")),
ui.tags.script(
"""
Shiny.addCustomMessageHandler('refresh', function(message) {
window.location.reload();
});
"""
),
ui.input_select(
id="select_theme",
label="Select a theme:",
selected=theme_obj.get("theme"),
choices=["superhero", "darkly", "sketchy"],
),
)
def server(input: Inputs, output: Outputs, session: Session):
print("server function was called")
print(theme_obj.get("theme"))
@reactive.Effect
@reactive.event(input.select_theme, ignore_none=True)
async def set_theme():
if input.select_theme() != theme_obj.get("theme"):
print("setting theme: ", input.select_theme())
theme_obj["theme"] = input.select_theme()
await session.send_custom_message("refresh", "")
app = App(app_ui, server, debug=False)
** working on making shinyswatch.theme_picker(app: App)
Nice! Thanks @schloerke. Should I remove the related issue for py-shiny?
EDIT: Just noticed you already closed that issue.
@vnijs Let me know how #12 works for you!
Remove the theme from your app
's UI and wrap your app
in shinyswatch.theme_picker(app)
Ex:
# ... at bottom of file: app.py
app = shinyswatch.theme_picker(App(app_ui, server))
Very nice @schloerke! Question: Could the theme dropdown be included in the navbar? Not without adding it back to the UI and forcing a refresh correct?
Not without adding it back to the UI and forcing a refresh correct?
Correct. It would require a "module" type approach that would need both a ui and server function.
I'm ok using the module approach if you find it more familiar. Altering the app
object is a new approach compared to R.
Thoughts?
To be honest, I never really used modules in R. Just used functions instead. Would love to see the example. Thanks @schloerke
Yes, it'd basically be shinyswatch.theme_picker_ui(*, default_theme: str, id: str)
and shinyswatch.theme_picker_server(*, id: str)
. Each function would live within the UI / server respectively.
py-shinyswatch looks outstanding. Thanks for creating this!
I'm almost surprised no one has asked this question yet since theme pickers seem to be very popular but ... is something like the below possible for shiny-for-python and py-shinyswatch?