Michael Rabinovich Cursor commited on
Commit
a0462d9
·
1 Parent(s): 5dd0c03

leaderboard: lock GT row via in-iframe scroll capped by max-height

Browse files

The previous attempt capped the gallery scroll box from JS using
window.parent.innerHeight, but inside HF's nested iframes that is not the
browser viewport, so the cap came out huge: the box never scrolled, the
iframe grew to full height, and the page scrolled the GT row away.

Instead, scroll inside the iframe itself (the column header + GT row are
sticky against the iframe viewport, which reliably works) and cap the
iframe with `max-height: 80vh` in app.py, where vh is the Gradio doc
viewport. fitIframe still shrinks the iframe to content for a compact
page when there are few rows; once content exceeds the cap the iframe
body scrolls and the GT row stays pinned. Drops the unreliable JS cap
and the parent-aware modal positioning (back to plain fixed centering).

Co-authored-by: Cursor <cursoragent@cursor.com>

Files changed (2) hide show
  1. app.py +8 -5
  2. gallery.py +9 -92
app.py CHANGED
@@ -793,13 +793,16 @@ def _gallery_iframe_html() -> str:
793
  rows, _render_proxy_url, _gt_proxy_url, _render_diff_proxy_url,
794
  )
795
  escaped = html.escape(doc, quote=True)
796
- # The gallery JS (`fitIframe`) resizes this iframe to its own content so the
797
- # page scrolls naturally in the parent (no oversized fixed box, no nested
798
- # scrollbar). The inline height is only the pre-script fallback for the case
799
- # where same-origin `frameElement` access is blocked.
 
 
 
800
  return (
801
  f'<iframe srcdoc="{escaped}" '
802
- 'style="width:100%; height:700px; border:0; display:block;" '
803
  'title="CADGenBench gallery"></iframe>'
804
  )
805
 
 
793
  rows, _render_proxy_url, _gt_proxy_url, _render_diff_proxy_url,
794
  )
795
  escaped = html.escape(doc, quote=True)
796
+ # The gallery JS (`fitIframe`) resizes this iframe to its own content so it
797
+ # is compact when there are few rows. `max-height` caps it: once the content
798
+ # is taller than the cap the iframe stops growing and its own body scrolls,
799
+ # which is what keeps the gallery's sticky column header + ground-truth row
800
+ # locked at the top. The cap is in `vh` here (reliable in the Gradio doc)
801
+ # rather than computed from JS (the nested-iframe parent height is not the
802
+ # browser viewport). The inline height is the pre-script fallback.
803
  return (
804
  f'<iframe srcdoc="{escaped}" '
805
+ 'style="width:100%; height:80vh; max-height:80vh; border:0; display:block;" '
806
  'title="CADGenBench gallery"></iframe>'
807
  )
808
 
gallery.py CHANGED
@@ -250,17 +250,7 @@ body {
250
  .section-caption { margin: 0 0 16px; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
251
  .section-caption b { color: var(--ink); font-weight: 600; }
252
 
253
- /* Scroll container: the column header + ground-truth row are `position:
254
- sticky` against this box, so they stay locked at the top while the
255
- submission rows scroll. `--gallery-max` is set in JS from the parent
256
- viewport height; the px value here is only the pre-script fallback.
257
- When the content is shorter than the cap, `overflow:auto` shows no
258
- scrollbar and the box just sizes to its rows. */
259
- .gallery {
260
- background: var(--panel); border: 1px solid var(--line);
261
- border-radius: var(--radius); box-shadow: var(--shadow); position: relative;
262
- max-height: var(--gallery-max, 640px); overflow: auto;
263
- }
264
  .grid-head, .grow {
265
  display: grid;
266
  grid-template-columns: 52px minmax(200px, 1.3fr) 160px repeat(var(--ncol, 4), minmax(140px, 1fr));
@@ -569,98 +559,27 @@ function openModal(fxId, sub) {
569
  document.getElementById('modalNote').innerHTML =
570
  'CAD score for this sample: <b>' + cad + '</b>. The full per-sample report '
571
  + '(shape similarity, interface, topology + 3D view) opens from the report viewer.';
572
- const back = document.getElementById('modalBack');
573
- back.classList.add('show');
574
- positionModalToView();
575
- attachModalViewSync();
576
  }
577
  function closeModal() {
578
  document.getElementById('modalBack').classList.remove('show');
579
- detachModalViewSync();
580
  }
581
  document.getElementById('modalClose').onclick = closeModal;
582
  document.getElementById('modalBack').onclick = (e) => { if (e.target.id === 'modalBack') closeModal(); };
583
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
584
 
585
- // --- Modal positioning ----------------------------------------------------
586
- // The page lives in a srcdoc iframe sized to its full content (see fitIframe),
587
- // so a plain `position: fixed` overlay would anchor to the iframe's full
588
- // height and land far below the fold. Instead we pin the overlay to the part
589
- // of the iframe currently visible inside the parent viewport. srcdoc iframes
590
- // are same-origin with the embedding document, so frameElement / parent are
591
- // readable; everything is wrapped in try/catch and falls back to a fixed,
592
- // viewport-centred overlay if that access is ever blocked.
593
- function positionModalToView() {
594
- const back = document.getElementById('modalBack');
595
- try {
596
- const fe = window.frameElement;
597
- const pv = window.parent;
598
- if (fe && pv) {
599
- const rect = fe.getBoundingClientRect(); // iframe box in parent viewport
600
- const docH = document.documentElement.scrollHeight;
601
- const visTop = Math.max(0, -rect.top); // iframe-doc y at top of view
602
- const visBottom = Math.min(docH, -rect.top + pv.innerHeight);
603
- if (visBottom > visTop) {
604
- back.style.position = 'absolute';
605
- back.style.left = '0';
606
- back.style.right = '0';
607
- back.style.bottom = 'auto';
608
- back.style.top = visTop + 'px';
609
- back.style.height = (visBottom - visTop) + 'px';
610
- return;
611
- }
612
- }
613
- } catch (e) { /* cross-origin / sandboxed -> fixed fallback below */ }
614
- back.style.position = 'fixed';
615
- back.style.top = '0'; back.style.left = '0';
616
- back.style.right = '0'; back.style.bottom = '0';
617
- back.style.height = '';
618
- }
619
-
620
- let _modalSync = null;
621
- function attachModalViewSync() {
622
- try {
623
- const pv = window.parent;
624
- _modalSync = () => positionModalToView();
625
- pv.addEventListener('scroll', _modalSync, { passive: true });
626
- pv.addEventListener('resize', _modalSync);
627
- } catch (e) { _modalSync = null; }
628
- }
629
- function detachModalViewSync() {
630
- try {
631
- const pv = window.parent;
632
- if (_modalSync) {
633
- pv.removeEventListener('scroll', _modalSync);
634
- pv.removeEventListener('resize', _modalSync);
635
- }
636
- } catch (e) { /* ignore */ }
637
- _modalSync = null;
638
- }
639
-
640
  // Pin the GT row exactly beneath the sticky column header.
641
  function syncHeadHeight() {
642
  const head = document.getElementById('gridHead');
643
  if (head) document.documentElement.style.setProperty('--head-h', head.offsetHeight + 'px');
644
  }
645
 
646
- // Cap the scroll box at a fraction of the *visible* (parent) viewport so the
647
- // GT row + header lock while submissions scroll inside it, without making the
648
- // page tall. When the rows are shorter than the cap the box just sizes to them
649
- // (no scrollbar). Reads the parent viewport (same-origin srcdoc); no-ops to the
650
- // CSS px fallback if that access is blocked.
651
- function capGallery() {
652
- try {
653
- const pv = window.parent;
654
- if (pv && pv.innerHeight) {
655
- const cap = Math.max(360, Math.round(pv.innerHeight * 0.78));
656
- document.documentElement.style.setProperty('--gallery-max', cap + 'px');
657
- }
658
- } catch (e) { /* keep CSS fallback */ }
659
- }
660
-
661
- // Size the iframe to its content so the page is as compact as the (capped)
662
- // gallery -- no oversized fixed box, no empty space when there are few rows.
663
- // No-ops if frameElement is unreadable (the wrapper then keeps its CSS height).
664
  function fitIframe() {
665
  try {
666
  const fe = window.frameElement;
@@ -669,11 +588,9 @@ function fitIframe() {
669
  }
670
 
671
  buildGallery();
672
- capGallery();
673
  fitIframe();
674
- function relayout() { syncHeadHeight(); capGallery(); fitIframe(); }
675
  window.addEventListener('resize', relayout);
676
- try { window.parent.addEventListener('resize', relayout); } catch (e) { /* ignore */ }
677
  if (window.ResizeObserver) new ResizeObserver(fitIframe).observe(document.body);
678
  if (document.fonts && document.fonts.ready) document.fonts.ready.then(relayout);
679
  """
 
250
  .section-caption { margin: 0 0 16px; font-size: 12.5px; color: var(--ink-soft); line-height: 1.5; }
251
  .section-caption b { color: var(--ink); font-weight: 600; }
252
 
253
+ .gallery { background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); position: relative; }
 
 
 
 
 
 
 
 
 
 
254
  .grid-head, .grow {
255
  display: grid;
256
  grid-template-columns: 52px minmax(200px, 1.3fr) 160px repeat(var(--ncol, 4), minmax(140px, 1fr));
 
559
  document.getElementById('modalNote').innerHTML =
560
  'CAD score for this sample: <b>' + cad + '</b>. The full per-sample report '
561
  + '(shape similarity, interface, topology + 3D view) opens from the report viewer.';
562
+ document.getElementById('modalBack').classList.add('show');
 
 
 
563
  }
564
  function closeModal() {
565
  document.getElementById('modalBack').classList.remove('show');
 
566
  }
567
  document.getElementById('modalClose').onclick = closeModal;
568
  document.getElementById('modalBack').onclick = (e) => { if (e.target.id === 'modalBack') closeModal(); };
569
  document.addEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); });
570
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
571
  // Pin the GT row exactly beneath the sticky column header.
572
  function syncHeadHeight() {
573
  const head = document.getElementById('gridHead');
574
  if (head) document.documentElement.style.setProperty('--head-h', head.offsetHeight + 'px');
575
  }
576
 
577
+ // Size the iframe to its content, but only up to the `max-height` the host sets
578
+ // on the iframe element (see app.py). With few rows the iframe shrinks to the
579
+ // content (compact, no empty box); once the content is taller than the cap the
580
+ // iframe stays at the cap and its own body scrolls -- which is what lets the
581
+ // sticky column header + ground-truth row stay locked at the top while the
582
+ // submission rows scroll under them. No-ops if frameElement is unreadable.
 
 
 
 
 
 
 
 
 
 
 
 
583
  function fitIframe() {
584
  try {
585
  const fe = window.frameElement;
 
588
  }
589
 
590
  buildGallery();
 
591
  fitIframe();
592
+ function relayout() { syncHeadHeight(); fitIframe(); }
593
  window.addEventListener('resize', relayout);
 
594
  if (window.ResizeObserver) new ResizeObserver(fitIframe).observe(document.body);
595
  if (document.fonts && document.fonts.ready) document.fonts.ready.then(relayout);
596
  """