HongzeFu commited on
Commit
8a7aab0
Β·
1 Parent(s): e56bc8f

force light mode

Browse files
gradio-web/main.py CHANGED
@@ -145,7 +145,9 @@ def main():
145
  debug=True,
146
  show_error=True,
147
  quiet=False,
 
148
  css=CSS,
 
149
  )
150
 
151
 
 
145
  debug=True,
146
  show_error=True,
147
  quiet=False,
148
+ theme=getattr(demo, "theme", None),
149
  css=CSS,
150
+ head=getattr(demo, "head", None),
151
  )
152
 
153
 
gradio-web/test/test_main_launch_config.py CHANGED
@@ -7,6 +7,8 @@ import types
7
  class _FakeDemo:
8
  def __init__(self):
9
  self.launch_kwargs = None
 
 
10
 
11
  def launch(self, **kwargs):
12
  self.launch_kwargs = kwargs
@@ -36,4 +38,6 @@ def test_main_launch_passes_ui_css(monkeypatch, reload_module):
36
  assert fake_demo.launch_kwargs is not None
37
  assert fake_demo.launch_kwargs["server_name"] == "0.0.0.0"
38
  assert fake_demo.launch_kwargs["server_port"] == 7861
 
39
  assert fake_demo.launch_kwargs["css"] == fake_ui_layout.CSS
 
 
7
  class _FakeDemo:
8
  def __init__(self):
9
  self.launch_kwargs = None
10
+ self.theme = "default"
11
+ self.head = "<script>window.__robommeForceLightTheme=()=>{};</script>"
12
 
13
  def launch(self, **kwargs):
14
  self.launch_kwargs = kwargs
 
38
  assert fake_demo.launch_kwargs is not None
39
  assert fake_demo.launch_kwargs["server_name"] == "0.0.0.0"
40
  assert fake_demo.launch_kwargs["server_port"] == 7861
41
+ assert fake_demo.launch_kwargs["theme"] == fake_demo.theme
42
  assert fake_demo.launch_kwargs["css"] == fake_ui_layout.CSS
43
+ assert fake_demo.launch_kwargs["head"] == fake_demo.head
gradio-web/test/test_ui_native_layout_contract.py CHANGED
@@ -45,6 +45,25 @@ def test_native_ui_css_excludes_header_title_from_global_font_size(reload_module
45
  assert "font-size: var(--text-xxl) !important;" in ui_layout.CSS
46
 
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  def test_native_ui_css_highlights_media_card_not_live_obs_transform(reload_module):
49
  ui_layout = reload_module("ui_layout")
50
 
@@ -84,6 +103,8 @@ def test_native_ui_config_contains_phase_machine_and_precheck_chain(reload_modul
84
 
85
  try:
86
  cfg = demo.get_config_file()
 
 
87
 
88
  elem_ids = {
89
  comp.get("props", {}).get("elem_id")
 
45
  assert "font-size: var(--text-xxl) !important;" in ui_layout.CSS
46
 
47
 
48
+ def test_native_ui_forces_light_theme_and_uses_light_overlay_baseline(reload_module):
49
+ ui_layout = reload_module("ui_layout")
50
+
51
+ css = ui_layout.CSS
52
+
53
+ assert "color-scheme: light !important;" in css
54
+ assert "background: rgba(255, 255, 255, 0.92) !important;" in css
55
+ assert "color: var(--body-text-color) !important;" in css
56
+ assert "body.dark," not in css
57
+ assert ".dark," not in css
58
+ assert ":root.dark" not in css
59
+ assert "rgba(15, 23, 42, 0.92)" not in css
60
+
61
+ assert "window.__robommeForceLightTheme = applyLightTheme;" in ui_layout.THEME_LOCK_HEAD
62
+ assert 'store.setItem("gradio-theme", "light");' in ui_layout.THEME_LOCK_HEAD
63
+ assert 'node.classList.remove("dark");' in ui_layout.THEME_LOCK_HEAD
64
+ assert "window.__robommeForceLightTheme();" in ui_layout.THEME_LOCK_JS
65
+
66
+
67
  def test_native_ui_css_highlights_media_card_not_live_obs_transform(reload_module):
68
  ui_layout = reload_module("ui_layout")
69
 
 
103
 
104
  try:
105
  cfg = demo.get_config_file()
106
+ assert cfg.get("theme") == "default"
107
+ assert cfg.get("head") == ui_layout.THEME_LOCK_HEAD
108
 
109
  elem_ids = {
110
  comp.get("props", {}).get("elem_id")
gradio-web/test/test_ui_phase_machine_runtime_e2e.py CHANGED
@@ -284,6 +284,36 @@ def _read_font_probe_snapshot(page) -> dict[str, str | None]:
284
  )
285
 
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  @pytest.fixture
288
  def font_size_probe_ui_url(monkeypatch):
289
  config_module = importlib.reload(importlib.import_module("config"))
@@ -589,6 +619,129 @@ def test_global_font_size_applies_except_header_title(font_size_probe_ui_url):
589
  browser.close()
590
 
591
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
592
  def test_phase_machine_runtime_flow_and_execute_precheck(phase_machine_ui_url):
593
  root_url, state = phase_machine_ui_url
594
 
 
284
  )
285
 
286
 
287
+ def _read_theme_snapshot(page) -> dict[str, str | bool | None]:
288
+ return page.evaluate(
289
+ """() => {
290
+ const html = document.documentElement;
291
+ const body = document.body;
292
+ const overlay = document.getElementById('loading_overlay_group');
293
+ const readStore = (store, key) => {
294
+ try {
295
+ return store.getItem(key);
296
+ } catch (error) {
297
+ return null;
298
+ }
299
+ };
300
+ return {
301
+ htmlHasDark: html ? html.classList.contains('dark') : null,
302
+ bodyHasDark: body ? body.classList.contains('dark') : null,
303
+ htmlTheme: html ? html.dataset.theme || null : null,
304
+ bodyTheme: body ? body.dataset.theme || null : null,
305
+ htmlInlineColorScheme: html ? html.style.colorScheme || null : null,
306
+ bodyInlineColorScheme: body ? body.style.colorScheme || null : null,
307
+ htmlColorScheme: html ? getComputedStyle(html).colorScheme : null,
308
+ bodyColorScheme: body ? getComputedStyle(body).colorScheme : null,
309
+ overlayBackground: overlay ? getComputedStyle(overlay).backgroundColor : null,
310
+ storedTheme: readStore(window.localStorage, 'theme'),
311
+ storedGradioTheme: readStore(window.localStorage, 'gradio-theme'),
312
+ };
313
+ }"""
314
+ )
315
+
316
+
317
  @pytest.fixture
318
  def font_size_probe_ui_url(monkeypatch):
319
  config_module = importlib.reload(importlib.import_module("config"))
 
619
  browser.close()
620
 
621
 
622
+ def test_create_ui_blocks_stays_light_under_dark_system_preference(monkeypatch):
623
+ ui_layout = importlib.reload(importlib.import_module("ui_layout"))
624
+
625
+ fake_obs = np.zeros((24, 24, 3), dtype=np.uint8)
626
+ fake_obs_img = Image.fromarray(fake_obs)
627
+
628
+ def fake_init_app(_request):
629
+ return (
630
+ "uid-1",
631
+ gr.update(visible=True),
632
+ fake_obs_img,
633
+ "ready",
634
+ gr.update(choices=[("pick", 0)], value=None),
635
+ "goal",
636
+ "No need for coordinates",
637
+ gr.update(value=None, visible=False),
638
+ gr.update(visible=False, interactive=False),
639
+ "PickXtimes (Episode 1)",
640
+ "Completed: 0",
641
+ gr.update(interactive=True),
642
+ gr.update(interactive=True),
643
+ gr.update(interactive=True),
644
+ gr.update(visible=False),
645
+ gr.update(visible=True),
646
+ gr.update(visible=True),
647
+ gr.update(value="hint"),
648
+ gr.update(visible=False),
649
+ gr.update(interactive=True),
650
+ )
651
+
652
+ monkeypatch.setattr(ui_layout, "init_app", fake_init_app)
653
+
654
+ demo = ui_layout.create_ui_blocks()
655
+ port = _free_port()
656
+ _app, root_url, _share_url = demo.launch(
657
+ server_name="127.0.0.1",
658
+ server_port=port,
659
+ prevent_thread_lock=True,
660
+ quiet=True,
661
+ show_error=True,
662
+ ssr_mode=False,
663
+ theme=ui_layout.APP_THEME,
664
+ css=ui_layout.CSS,
665
+ head=ui_layout.THEME_LOCK_HEAD,
666
+ )
667
+ _wait_http_ready(root_url)
668
+
669
+ try:
670
+ with sync_playwright() as p:
671
+ browser = p.chromium.launch(headless=True)
672
+ context = browser.new_context(
673
+ viewport={"width": 1280, "height": 900},
674
+ color_scheme="dark",
675
+ )
676
+ context.add_init_script(
677
+ """
678
+ window.localStorage.setItem('theme', 'dark');
679
+ window.localStorage.setItem('gradio-theme', 'dark');
680
+ """
681
+ )
682
+ page = context.new_page()
683
+ page.goto(root_url, wait_until="domcontentloaded")
684
+
685
+ page.wait_for_function(
686
+ """() => {
687
+ const html = document.documentElement;
688
+ const body = document.body;
689
+ if (!html || !body) return false;
690
+ return (
691
+ typeof window.__robommeForceLightTheme === 'function' &&
692
+ !html.classList.contains('dark') &&
693
+ !body.classList.contains('dark') &&
694
+ html.dataset.theme === 'light' &&
695
+ body.dataset.theme === 'light' &&
696
+ html.style.colorScheme === 'light' &&
697
+ body.style.colorScheme === 'light'
698
+ );
699
+ }""",
700
+ timeout=15000,
701
+ )
702
+
703
+ snapshot = _read_theme_snapshot(page)
704
+ assert snapshot["htmlHasDark"] is False
705
+ assert snapshot["bodyHasDark"] is False
706
+ assert snapshot["htmlTheme"] == "light"
707
+ assert snapshot["bodyTheme"] == "light"
708
+ assert snapshot["htmlInlineColorScheme"] == "light"
709
+ assert snapshot["bodyInlineColorScheme"] == "light"
710
+ assert snapshot["storedTheme"] == "light"
711
+ assert snapshot["storedGradioTheme"] == "light"
712
+
713
+ page.reload(wait_until="domcontentloaded")
714
+ page.wait_for_function(
715
+ """() => {
716
+ const html = document.documentElement;
717
+ const body = document.body;
718
+ return (
719
+ !!html &&
720
+ !!body &&
721
+ typeof window.__robommeForceLightTheme === 'function' &&
722
+ !html.classList.contains('dark') &&
723
+ !body.classList.contains('dark') &&
724
+ html.dataset.theme === 'light' &&
725
+ body.dataset.theme === 'light'
726
+ );
727
+ }""",
728
+ timeout=15000,
729
+ )
730
+
731
+ reloaded_snapshot = _read_theme_snapshot(page)
732
+ assert reloaded_snapshot["htmlHasDark"] is False
733
+ assert reloaded_snapshot["bodyHasDark"] is False
734
+ assert reloaded_snapshot["htmlInlineColorScheme"] == "light"
735
+ assert reloaded_snapshot["bodyInlineColorScheme"] == "light"
736
+ assert reloaded_snapshot["storedTheme"] == "light"
737
+ assert reloaded_snapshot["storedGradioTheme"] == "light"
738
+
739
+ context.close()
740
+ browser.close()
741
+ finally:
742
+ demo.close()
743
+
744
+
745
  def test_phase_machine_runtime_flow_and_execute_precheck(phase_machine_ui_url):
746
  root_url, state = phase_machine_ui_url
747
 
gradio-web/ui_layout.py CHANGED
@@ -44,6 +44,8 @@ PHASE_DEMO_VIDEO = "demo_video"
44
  PHASE_ACTION_KEYPOINT = "action_keypoint"
45
  PHASE_EXECUTION_PLAYBACK = "execution_playback"
46
 
 
 
47
 
48
  # Deprecated: no legacy runtime JS logic in native Gradio mode.
49
  SYNC_JS = ""
@@ -234,6 +236,97 @@ LIVE_OBS_CLIENT_RESIZE_JS = r"""
234
  """
235
 
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  CSS = f"""
238
  :root {{
239
  --body-text-size: {UI_GLOBAL_FONT_SIZE} !important;
@@ -248,6 +341,7 @@ CSS = f"""
248
  --button-small-text-size: {UI_GLOBAL_FONT_SIZE} !important;
249
  --section-header-text-size: {UI_GLOBAL_FONT_SIZE} !important;
250
  --text-md: {UI_GLOBAL_FONT_SIZE} !important;
 
251
  }}
252
 
253
  .native-card {{
@@ -262,6 +356,7 @@ CSS = f"""
262
  inset: 0 !important;
263
  z-index: 9999 !important;
264
  background: rgba(255, 255, 255, 0.92) !important;
 
265
  text-align: center !important;
266
  }}
267
 
@@ -461,6 +556,8 @@ def create_ui_blocks():
461
 
462
  with gr.Blocks(title="Oracle Planner Interface") as demo:
463
  demo.css = CSS
 
 
464
 
465
  gr.Markdown("## RoboMME Interactive Demo πŸš€πŸš€πŸš€", elem_id="header_title")
466
  with gr.Row():
@@ -898,6 +995,12 @@ def create_ui_blocks():
898
  show_progress="hidden",
899
  )
900
 
 
 
 
 
 
 
901
  demo.load(
902
  fn=None,
903
  js=LIVE_OBS_CLIENT_RESIZE_JS,
 
44
  PHASE_ACTION_KEYPOINT = "action_keypoint"
45
  PHASE_EXECUTION_PLAYBACK = "execution_playback"
46
 
47
+ APP_THEME = gr.themes.Default()
48
+
49
 
50
  # Deprecated: no legacy runtime JS logic in native Gradio mode.
51
  SYNC_JS = ""
 
236
  """
237
 
238
 
239
+ THEME_LOCK_HEAD = r"""
240
+ <script>
241
+ (() => {
242
+ const applyLightTheme = () => {
243
+ const normalizeThemeState = (store) => {
244
+ try {
245
+ store.setItem("theme", "light");
246
+ store.setItem("color-scheme", "light");
247
+ store.setItem("gradio-theme", "light");
248
+ for (const key of Object.keys(store)) {
249
+ if (!/theme|color-scheme/i.test(key)) {
250
+ continue;
251
+ }
252
+ const value = store.getItem(key);
253
+ if (typeof value === "string" && /dark/i.test(value)) {
254
+ store.setItem(key, value.replace(/dark/gi, "light"));
255
+ }
256
+ }
257
+ } catch (error) {
258
+ console.debug("Failed to normalize theme state", error);
259
+ }
260
+ };
261
+ const normalizeNode = (node) => {
262
+ if (!node) {
263
+ return;
264
+ }
265
+ if (node.classList.contains("dark")) {
266
+ node.classList.remove("dark");
267
+ }
268
+ if (node.dataset.theme !== "light") {
269
+ node.dataset.theme = "light";
270
+ }
271
+ if (node.getAttribute("data-color-scheme") !== "light") {
272
+ node.setAttribute("data-color-scheme", "light");
273
+ }
274
+ if (node.style.colorScheme !== "light") {
275
+ node.style.colorScheme = "light";
276
+ }
277
+ };
278
+
279
+ normalizeThemeState(window.localStorage);
280
+ normalizeThemeState(window.sessionStorage);
281
+ normalizeNode(document.documentElement);
282
+ normalizeNode(document.body);
283
+ };
284
+
285
+ applyLightTheme();
286
+ window.__robommeForceLightTheme = applyLightTheme;
287
+
288
+ if (window.__robommeThemeLockInstalled) {
289
+ return;
290
+ }
291
+
292
+ const observer = new MutationObserver(() => applyLightTheme());
293
+ observer.observe(document.documentElement, {
294
+ attributes: true,
295
+ attributeFilter: ["class", "data-theme", "style"],
296
+ });
297
+
298
+ const attachBodyObserver = () => {
299
+ if (!document.body || document.body.dataset.robommeThemeObserved === "1") {
300
+ return;
301
+ }
302
+ document.body.dataset.robommeThemeObserved = "1";
303
+ observer.observe(document.body, {
304
+ attributes: true,
305
+ attributeFilter: ["class", "data-theme", "style"],
306
+ });
307
+ };
308
+
309
+ attachBodyObserver();
310
+ document.addEventListener("DOMContentLoaded", attachBodyObserver, { once: true });
311
+ window.addEventListener("load", applyLightTheme, { once: true });
312
+ window.setTimeout(applyLightTheme, 0);
313
+ window.setTimeout(applyLightTheme, 100);
314
+ window.__robommeThemeLockInstalled = true;
315
+ })();
316
+ </script>
317
+ """
318
+
319
+
320
+ THEME_LOCK_JS = r"""
321
+ () => {
322
+ if (typeof window.__robommeForceLightTheme === "function") {
323
+ window.__robommeForceLightTheme();
324
+ window.setTimeout(window.__robommeForceLightTheme, 0);
325
+ }
326
+ }
327
+ """
328
+
329
+
330
  CSS = f"""
331
  :root {{
332
  --body-text-size: {UI_GLOBAL_FONT_SIZE} !important;
 
341
  --button-small-text-size: {UI_GLOBAL_FONT_SIZE} !important;
342
  --section-header-text-size: {UI_GLOBAL_FONT_SIZE} !important;
343
  --text-md: {UI_GLOBAL_FONT_SIZE} !important;
344
+ color-scheme: light !important;
345
  }}
346
 
347
  .native-card {{
 
356
  inset: 0 !important;
357
  z-index: 9999 !important;
358
  background: rgba(255, 255, 255, 0.92) !important;
359
+ color: var(--body-text-color) !important;
360
  text-align: center !important;
361
  }}
362
 
 
556
 
557
  with gr.Blocks(title="Oracle Planner Interface") as demo:
558
  demo.css = CSS
559
+ demo.theme = APP_THEME
560
+ demo.head = THEME_LOCK_HEAD
561
 
562
  gr.Markdown("## RoboMME Interactive Demo πŸš€πŸš€πŸš€", elem_id="header_title")
563
  with gr.Row():
 
995
  show_progress="hidden",
996
  )
997
 
998
+ demo.load(
999
+ fn=None,
1000
+ js=THEME_LOCK_JS,
1001
+ queue=False,
1002
+ )
1003
+
1004
  demo.load(
1005
  fn=None,
1006
  js=LIVE_OBS_CLIENT_RESIZE_JS,