Members
(static, constant) DEFAULT_BODY_SPACING
The itemSpacings the XML declares on #scrollContent, named here because clearReportNodes has to restore it after a structured body widened it.
- Default Value
- 20
(static, constant) DIALOG_WIDTH
This dialog's OWN geometry. The vertical flow (padding, title, accent rule, gaps) is shared — see source/utils/dialogLayout.bs.
WIDTH is this dialog's to choose; HEIGHT is not, and used to be. The panel was a fixed 1602x762 on the reasoning that a scrolling body cannot decide its own size. That is half true and it was the wrong half: the WIDTH is what decides where the text wraps, and the width is fixed, so the text's natural height is fully known before any height decision. So the panel is content-derived like every other dialog now, and the scroll viewport reads the body height the flow hands back — clamped at PANEL_MAX_HEIGHT, which is the point at which it scrolls. A short description gets a small panel instead of ~200px of dead space; a long one gets 924px, which is 162px MORE than the old fixed height.
1602 rather than 1600 because a width divisible by 6 puts the centred panelX on the 720p output grid ((1920 - w) / 2 = 960 - w/2); computeDialogLayout snaps panelY. 1600 put every edge off it — the most visible chrome in the app's largest panel.
- Default Value
- 1602
(static, constant) REPORT_LABEL_WIDTH
Structured-body geometry. Both label columns are fixed rather than measured: measuring the widest label needs a rendered pass, and this dialog already learned the hard way that nothing may depend on which pass it is in. Both are multiples of 6 so the value column's left edge lands on the 720p output grid. Wide enough that no known label has to wrap, because wrapping is the expensive fix here: a wrapping label must be render-tracked (it could be the tallest thing in its row), and tracked nodes are quadratic — see trackReportNode.
MEASURED on a Stick 4K, not estimated from character counts, which got this wrong in both directions — the longest string is not the widest one:
Total Bitrate 179px the longest ENGLISH row label Taxa de bits total 250px pt_BR Formato dos pixeis 271px pt / gl / es_AR Format slikovnih pik 285px sl — the longest by CHARACTERS (20), fits Formatu-edukiontzia 302px eu — the only real translation over 300 Hardware Acceleration 329px not a value we ship, but the shape of one
300 clipped eu by two pixels. 360 clears every string above with room to spare, stays a multiple of 6 so the value column's left edge lands on the 720p output grid, and costs the value column 60px it does not need — the longest value is a file path, which wraps to maxLines = 6 either way.
- Default Value
- 360
(static, constant) REPORT_ROW_SPACING
- Default Value
- 6
(static, constant) REPORT_SECTION_SPACING
- Default Value
- 24
(static, constant) REPORT_WIDE_LABEL_WIDTH
Reason rows carry a Jellyfin reason code in the label column ("VideoRangeTypeNotSupported"), which needs roughly twice the room a "Codec" does. Measured on a Stick 4K, the widest code in Jellyfin's vocabulary renders at 449px ("VideoRangeTypeNotSupported"; "ContainerBitrateExceedsLimit" is longer in characters but narrower at 437px), so this carries ~100px of headroom for a code the server adds later. Reason codes are never translated, so unlike the column above that measurement holds in every locale.
- Default Value
- 552
(static, constant) SCROLLBAR_MARGIN
12, not 8: the text column's right edge is body.width - THUMB - MARGIN, so a margin off the 6px scale pushed that edge off the 720p grid.
- Default Value
- 12
(static, constant) SCROLLBAR_THUMB_WIDTH
- Default Value
- 12
(static, constant) SCROLLBAR_TRACK_INSET
Centring offset for the 3px track inside the 12px thumb gutter. The true centre is 4.5, which is both fractional AND off the grid — for a 3px hard edge that is the worst case, so it sits one and a half pixels right of centre instead, which nothing can see and the output grid can.
- Default Value
- 6
(static, constant) SCROLLBAR_TRACK_WIDTH
- Default Value
- 3
(static, constant) SCROLL_STEP
- Default Value
- 150
Methods
(static) applyLayout() → {void}
Measure, then flow. Every input to the panel's height has to have settled before the panel can be sized, so each one that has not returns early and comes back through its own observer — the same measure-or-return shape JRDialog uses.
- Type:
- void
(static) applyScrollBounds() → {void}
Derive the scroll state from the two heights the layout just settled. The body is clamped at PANEL_MAX_HEIGHT by computeDialogLayout, so maxScroll > 0 is exactly "the content did not fit" — this dialog never re-derives that ceiling.
- Type:
- void
(static) bodyLabelsMeasured() → {boolean}
True once EVERY label this dialog expects to show reports a rendered height. Each label carries its own renderTracking observer, so each brings us back at least once — this only decides whether the pass we are in may be trusted, not whether another one is coming.
- Type:
- boolean
(static) buildReportNodes(sections) → {void}
| Name | Type | Description |
|---|---|---|
sections | object |
- Type:
- void
(static) buildReportRow(row, labelWidth) → {object}
One label/value row.
The label column is LabelSecondary and the value LabelPrimary, so the eye lands on the data rather than on the field names — the same emphasis split the theme table describes for text. The value wraps (a file path needs two or three lines); the label never does, which is what keeps the columns aligned.
| Name | Type | Description |
|---|---|---|
row | object | |
labelWidth | integer |
- Type:
- object
(static) cancelDialog() → {void}
Cancel this dialog from OUTSIDE — see cancelOpenDialog() in source/utils/dialogs.bs. This dialog is read-only and has no result field, so there is nothing to deliver: cancelling it is exactly closing it.
- Type:
- void
(static) clearReportNodes() → {void}
- Type:
- void
(static) closeDialog() → {void}
Dismiss this overlay. It is appended directly to the scene by FocusableOverview, so closing is a removeChild from our parent + restoring focus to the element that opened us (returnFocusTo).
- Type:
- void
(static) hasBodyText() → {boolean}
True when this dialog has anything to show in its scrolling body. Used to tell "nothing to render" apart from "not rendered yet", which otherwise both read as a measured height of zero and would park applyLayout forever.
- Type:
- boolean
(static) init() → {void}
- Type:
- void
(static) okButtonHeight() → {integer}
Rendered height of the OK button, or the shared fallback before it sizes itself. The fallback and the reasoning for it live in dialogLayout.bs, where JRDialog's row and QuickConnectDialog's row read the same one.
- Type:
- integer
(static) onFocusChanged() → {void}
- Type:
- void
(static) onKeyEvent(key, press) → {boolean}
| Name | Type | Description |
|---|---|---|
key | string | |
press | boolean |
- Type:
- boolean
(static) onOkButtonReady() → {void}
- Type:
- void
(static) onOpeningAnnouncementDue() → {void}
Announce the dialog once the platform's own focus announcement is out of the way. The overview text IS the announcement here: this dialog exists to be read, so a screen-reader user opening it wants its contents, not just its title.
- Type:
- void
(static) onOverviewChanged() → {void}
- Type:
- void
(static) onSectionsChanged() → {void}
The structured body arrived, or was refreshed.
Two paths, and choosing between them is the whole reason this dialog can carry a LIVE report. When the row ids are unchanged — the ordinary case, because a refresh re-reads the same stream — only the text of the value labels is rewritten. No node is created or destroyed, the body's height does not move, and neither does the user's scroll position or focus. When the shape genuinely differs (a transcode started, a section appeared), everything is rebuilt and the panel re-flows.
The in-place path deliberately does NOT call applyLayout(). A refreshed figure occupies the same single line as the one it replaces, so re-flowing would be a no-op that risks yanking the panel out from under someone mid-read.
- Type:
- void
(static) onTaglineChanged() → {void}
Dynamically creates or removes the tagline label based on tagline text
- Type:
- void
(static) onTaglineRendered() → {void}
Tagline has rendered — its height is part of the body, so the PANEL changes, not just the scroll bounds.
- Type:
- void
(static) onTextRendered() → {void}
Once the text has rendered its height is known, which is the last input the panel height was waiting on.
- Type:
- void
(static) onTitleChanged() → {void}
- Type:
- void
(static) onTitleMeasured() → {void}
- Type:
- void
(static) reportNarrationLines() → {object}
The structured body, spoken.
Without this a report dialog announces its title and then falls silent, because the opening announcement reads overview and a report sets sections instead. Each row is read as "label, value" so the pairing survives being spoken — the two-column alignment that carries it visually is worth nothing to a listener.
- Type:
- object
(static) reportShapeMatches(sections) → {boolean}
True when the incoming sections carry exactly the row ids already on screen, in the same order. Order is part of it: two reports with the same ids in a different order are a different document, and reusing nodes across that would silently reorder the labels.
| Name | Type | Description |
|---|---|---|
sections | object |
- Type:
- boolean
(static) settleLayout() → {void}
Lay out NOW, called by presentOverlayDialog once this dialog is attached.
The dialog is hidden until this returns, so there is no renderTracking to wait on — and none is needed, because localBoundingRect() answers while hidden. See dialogReveal.bs.
- Type:
- void
(static) trackReportNode(node) → {void}
Every label whose rendered height feeds the panel gets a renderTracking observer, for the same reason the overview label has one: the panel cannot be sized until they have all reported. Only WRAPPING labels are tracked — a single-line label column cannot change the row height, and each tracked node is expensive twice over: it adds a settle signal AND a member to the set every other settle signal re-measures. Measured on a Stick 4K, one localBoundingRect() read costs 23.05 µs, so that set is quadratic in real time — 26 tracked nodes is ~15.6 ms of render-thread work per open and 46 would be ~48.8 ms. Do not track a node whose height cannot vary.
| Name | Type | Description |
|---|---|---|
node | object |
- Type:
- void
(static) updateReportValues(sections) → {void}
Rewrite the values whose text actually changed. Skipping the unchanged ones is not just a saving: assigning to a Label's text re-renders it, and doing that to every row five seconds apart would repaint the whole report to update two numbers.
| Name | Type | Description |
|---|---|---|
sections | object |
- Type:
- void
(static) updateScrollThumb() → {void}
Updates the scroll thumb position to reflect the current scroll position
- Type:
- void
(static) updateScrollbarFocus(isFocused) → {void}
Updates scrollbar thumb color based on focus state
| Name | Type | Description |
|---|---|---|
isFocused | boolean |
- Type:
- void