Spaces:
Running
Running
Apply theme synchronously before first paint
#9
by volivers - opened
app.py
CHANGED
|
@@ -2383,6 +2383,46 @@ ATTRIBUTION_HTML = """
|
|
| 2383 |
"""
|
| 2384 |
|
| 2385 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2386 |
# JS injected once; the buttons call pickInstrument() which writes the chosen
|
| 2387 |
# value into the hidden Gradio textbox for that group and triggers its change.
|
| 2388 |
SELECTION_JS = """
|
|
@@ -2393,6 +2433,11 @@ SELECTION_JS = """
|
|
| 2393 |
document.body.classList.toggle('dark', dark);
|
| 2394 |
const container = document.querySelector('.gradio-container');
|
| 2395 |
if (container) container.classList.toggle('dark', dark);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2396 |
}
|
| 2397 |
|
| 2398 |
function toggleDark() {
|
|
@@ -2409,14 +2454,22 @@ SELECTION_JS = """
|
|
| 2409 |
window._lolaby_applyTheme = applyTheme;
|
| 2410 |
window._lolaby_toggleDark = toggleDark;
|
| 2411 |
|
| 2412 |
-
|
| 2413 |
-
|
| 2414 |
-
|
| 2415 |
-
|
| 2416 |
-
|
| 2417 |
-
|
|
|
|
|
|
|
| 2418 |
applyTheme(isDark);
|
| 2419 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2420 |
})();
|
| 2421 |
|
| 2422 |
// Ordered multi-select picker. Up to 2 instruments.
|
|
@@ -2590,8 +2643,16 @@ function flashComboHint() {
|
|
| 2590 |
"""
|
| 2591 |
|
| 2592 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2593 |
with gr.Blocks(css_paths="style.css", title="Lolaby", theme=gr.themes.Citrus(),
|
| 2594 |
-
head=
|
| 2595 |
gr.HTML("""
|
| 2596 |
<div style="text-align:right; padding: 12px 18px 0;">
|
| 2597 |
<label class="dark-toggle-label">
|
|
@@ -3051,7 +3112,7 @@ gr.mount_gradio_app(
|
|
| 3051 |
path="/",
|
| 3052 |
theme=gr.themes.Citrus(),
|
| 3053 |
css_paths="style.css",
|
| 3054 |
-
head=
|
| 3055 |
)
|
| 3056 |
|
| 3057 |
|
|
|
|
| 2383 |
"""
|
| 2384 |
|
| 2385 |
|
| 2386 |
+
# ---------------------------------------------------------------------------
|
| 2387 |
+
# EARLY_THEME_JS β must run as the FIRST thing in <head>, synchronously,
|
| 2388 |
+
# before any CSS/markup is painted.
|
| 2389 |
+
#
|
| 2390 |
+
# Why: the old approach decided light vs. dark inside a
|
| 2391 |
+
# `window.addEventListener('load', ...)` handler. `load` fires only after
|
| 2392 |
+
# the ENTIRE page (styles, fonts, the whole Gradio bundle) has finished
|
| 2393 |
+
# loading β by which point the page has already been painted once in the
|
| 2394 |
+
# default (light) theme. For anyone whose browser/OS prefers dark mode (or
|
| 2395 |
+
# who has `?__theme=dark` in the URL), every reload showed a flash of the
|
| 2396 |
+
# light theme that then snapped/animated to dark β the reported "flicker".
|
| 2397 |
+
#
|
| 2398 |
+
# Fix: read the theme preference (URL param > OS preference) and add the
|
| 2399 |
+
# `.dark` class to <html> immediately, in a blocking inline script placed
|
| 2400 |
+
# before any stylesheet. Since all theme tokens live on `:root`/`.dark`
|
| 2401 |
+
# (style.css), this means the very first paint already has the correct
|
| 2402 |
+
# variables β there's nothing to flip after the fact, so no flash and no
|
| 2403 |
+
# transition to animate.
|
| 2404 |
+
# ---------------------------------------------------------------------------
|
| 2405 |
+
EARLY_THEME_JS = """
|
| 2406 |
+
(function() {
|
| 2407 |
+
try {
|
| 2408 |
+
var url = new URL(window.location.href);
|
| 2409 |
+
var isDark;
|
| 2410 |
+
if (url.searchParams.has('__theme')) {
|
| 2411 |
+
isDark = url.searchParams.get('__theme') === 'dark';
|
| 2412 |
+
} else {
|
| 2413 |
+
isDark = !!(window.matchMedia &&
|
| 2414 |
+
window.matchMedia('(prefers-color-scheme: dark)').matches);
|
| 2415 |
+
}
|
| 2416 |
+
if (isDark) {
|
| 2417 |
+
document.documentElement.classList.add('dark');
|
| 2418 |
+
}
|
| 2419 |
+
} catch (e) {
|
| 2420 |
+
// If anything above throws (e.g. matchMedia unavailable), just fall
|
| 2421 |
+
// back to the light-mode default rather than breaking the page.
|
| 2422 |
+
}
|
| 2423 |
+
})();
|
| 2424 |
+
"""
|
| 2425 |
+
|
| 2426 |
# JS injected once; the buttons call pickInstrument() which writes the chosen
|
| 2427 |
# value into the hidden Gradio textbox for that group and triggers its change.
|
| 2428 |
SELECTION_JS = """
|
|
|
|
| 2433 |
document.body.classList.toggle('dark', dark);
|
| 2434 |
const container = document.querySelector('.gradio-container');
|
| 2435 |
if (container) container.classList.toggle('dark', dark);
|
| 2436 |
+
// Keep the visible toggle switch in sync with the active theme β
|
| 2437 |
+
// without this, a theme applied via EARLY_THEME_JS (system
|
| 2438 |
+
// preference or ?__theme=dark) left the switch showing "off".
|
| 2439 |
+
const toggleBtn = document.getElementById('dark-toggle-btn');
|
| 2440 |
+
if (toggleBtn) toggleBtn.checked = dark;
|
| 2441 |
}
|
| 2442 |
|
| 2443 |
function toggleDark() {
|
|
|
|
| 2454 |
window._lolaby_applyTheme = applyTheme;
|
| 2455 |
window._lolaby_toggleDark = toggleDark;
|
| 2456 |
|
| 2457 |
+
// EARLY_THEME_JS already set the `.dark` class on <html> (synchronously,
|
| 2458 |
+
// before first paint) if needed. Here we just propagate that class to
|
| 2459 |
+
// <body>/.gradio-container and sync the toggle switch β and we do it on
|
| 2460 |
+
// DOMContentLoaded (as soon as the DOM exists) rather than `load` (which
|
| 2461 |
+
// waits for every image/font/asset), so there's no extra delay where the
|
| 2462 |
+
// body/container could render with mismatched classes.
|
| 2463 |
+
function syncThemeClasses() {
|
| 2464 |
+
const isDark = document.documentElement.classList.contains('dark');
|
| 2465 |
applyTheme(isDark);
|
| 2466 |
+
}
|
| 2467 |
+
|
| 2468 |
+
if (document.readyState === 'loading') {
|
| 2469 |
+
document.addEventListener('DOMContentLoaded', syncThemeClasses);
|
| 2470 |
+
} else {
|
| 2471 |
+
syncThemeClasses();
|
| 2472 |
+
}
|
| 2473 |
})();
|
| 2474 |
|
| 2475 |
// Ordered multi-select picker. Up to 2 instruments.
|
|
|
|
| 2643 |
"""
|
| 2644 |
|
| 2645 |
|
| 2646 |
+
# EARLY_THEME_JS comes FIRST (before css_paths/style.css is linked in the
|
| 2647 |
+
# head) so the .dark class is set before any themed CSS rule is evaluated β
|
| 2648 |
+
# that's what eliminates the light->dark flash on reload. SELECTION_JS comes
|
| 2649 |
+
# after; it's fine for it to load alongside/after stylesheets since it only
|
| 2650 |
+
# touches non-paint-critical bookkeeping (button wiring, class sync, etc.).
|
| 2651 |
+
HEAD_HTML = f"<script>{EARLY_THEME_JS}</script><script>{SELECTION_JS}</script>"
|
| 2652 |
+
|
| 2653 |
+
|
| 2654 |
with gr.Blocks(css_paths="style.css", title="Lolaby", theme=gr.themes.Citrus(),
|
| 2655 |
+
head=HEAD_HTML) as demo:
|
| 2656 |
gr.HTML("""
|
| 2657 |
<div style="text-align:right; padding: 12px 18px 0;">
|
| 2658 |
<label class="dark-toggle-label">
|
|
|
|
| 3112 |
path="/",
|
| 3113 |
theme=gr.themes.Citrus(),
|
| 3114 |
css_paths="style.css",
|
| 3115 |
+
head=HEAD_HTML,
|
| 3116 |
)
|
| 3117 |
|
| 3118 |
|