From bb02c0f9b9b3046aa888e7d80f32fc55c4575928 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Sat, 4 Jul 2026 21:06:02 +0200 Subject: [PATCH] Enlarge actor overlay images and replace broken link with in-player popup Clicking an actor card no longer navigates the SPA away from the player (which lost playback position and often failed for person items). It now opens a closeable in-player detail pop-up with a larger portrait and full overview, dismissible via the close button, Back/Escape, the backdrop, or by pressing play. On-screen card portraits enlarged from 80px to 120px. --- Jellyfin.Plugin.JRay/Web/jray-overlay.js | 144 +++++++++++++++++++++-- 1 file changed, 136 insertions(+), 8 deletions(-) diff --git a/Jellyfin.Plugin.JRay/Web/jray-overlay.js b/Jellyfin.Plugin.JRay/Web/jray-overlay.js index d0ffa6e..79726e9 100644 --- a/Jellyfin.Plugin.JRay/Web/jray-overlay.js +++ b/Jellyfin.Plugin.JRay/Web/jray-overlay.js @@ -4,6 +4,7 @@ var POLL_INTERVAL_MS = 1000; var OVERVIEW_MAX_LENGTH = 160; var overlayEl = null; + var detailEl = null; var personCache = {}; function truncate(text, maxLength) { @@ -47,7 +48,18 @@ }); } + function removeDetail() { + if (detailEl && detailEl.parentNode) { + detailEl.parentNode.removeChild(detailEl); + } + + detailEl = null; + document.removeEventListener('keydown', onDetailKeydown, true); + } + function removeOverlay() { + removeDetail(); + if (overlayEl && overlayEl.parentNode) { overlayEl.parentNode.removeChild(overlayEl); } @@ -55,6 +67,119 @@ overlayEl = null; } + function onDetailKeydown(event) { + // Back button on TV remotes / keyboards maps to Escape / Backspace. + if (event.key === 'Escape' || event.key === 'Backspace' || event.keyCode === 27 || event.keyCode === 8) { + event.stopPropagation(); + event.preventDefault(); + removeDetail(); + } + } + + // A larger, closeable "pop-up" card shown over the video when an actor is + // clicked. It stays inside the player so playback position is never lost. + function showDetail(container, actor, person) { + removeDetail(); + + detailEl = document.createElement('div'); + detailEl.className = 'jrayActorDetail'; + detailEl.style.position = 'absolute'; + detailEl.style.top = '0'; + detailEl.style.left = '0'; + detailEl.style.right = '0'; + detailEl.style.bottom = '0'; + detailEl.style.zIndex = '10000'; + detailEl.style.display = 'flex'; + detailEl.style.alignItems = 'center'; + detailEl.style.justifyContent = 'center'; + detailEl.style.background = 'rgba(0, 0, 0, 0.6)'; + detailEl.style.pointerEvents = 'auto'; + + // Click on the dimmed backdrop closes the pop-up. + detailEl.addEventListener('click', function (event) { + if (event.target === detailEl) { + removeDetail(); + } + }); + + var panel = document.createElement('div'); + panel.style.position = 'relative'; + panel.style.display = 'flex'; + panel.style.gap = '24px'; + panel.style.maxWidth = '720px'; + panel.style.width = '80%'; + panel.style.maxHeight = '80%'; + panel.style.overflowY = 'auto'; + panel.style.background = 'rgba(20, 20, 20, 0.96)'; + panel.style.color = '#fff'; + panel.style.padding = '24px'; + panel.style.borderRadius = '10px'; + panel.style.boxShadow = '0 8px 32px rgba(0, 0, 0, 0.6)'; + + if (person && person.ImageTags && person.ImageTags.Primary) { + var img = document.createElement('img'); + img.src = window.ApiClient.getImageUrl(actor.jellyfin_id, { + type: 'Primary', + maxHeight: 400, + tag: person.ImageTags.Primary + }); + img.style.height = '300px'; + img.style.width = 'auto'; + img.style.borderRadius = '8px'; + img.style.objectFit = 'cover'; + img.style.flexShrink = '0'; + panel.appendChild(img); + } + + var text = document.createElement('div'); + text.style.flex = '1'; + + var name = document.createElement('div'); + name.style.fontWeight = 'bold'; + name.style.fontSize = '24px'; + name.style.marginBottom = '12px'; + name.textContent = actor.name; + text.appendChild(name); + + if (person && person.Overview) { + var overview = document.createElement('div'); + overview.style.fontSize = '15px'; + overview.style.lineHeight = '1.5'; + overview.style.opacity = '0.9'; + overview.textContent = person.Overview; + text.appendChild(overview); + } + + panel.appendChild(text); + + var closeBtn = document.createElement('button'); + closeBtn.type = 'button'; + closeBtn.setAttribute('aria-label', 'Close'); + closeBtn.textContent = '✕'; + closeBtn.style.position = 'absolute'; + closeBtn.style.top = '8px'; + closeBtn.style.right = '8px'; + closeBtn.style.width = '32px'; + closeBtn.style.height = '32px'; + closeBtn.style.border = 'none'; + closeBtn.style.borderRadius = '50%'; + closeBtn.style.background = 'rgba(255, 255, 255, 0.15)'; + closeBtn.style.color = '#fff'; + closeBtn.style.fontSize = '16px'; + closeBtn.style.cursor = 'pointer'; + closeBtn.addEventListener('click', function (event) { + event.stopPropagation(); + removeDetail(); + }); + panel.appendChild(closeBtn); + + detailEl.appendChild(panel); + container.appendChild(detailEl); + + // Back button (Escape/Backspace) closes the pop-up first. + document.addEventListener('keydown', onDetailKeydown, true); + } + function showOverlay(video, actors) { removeOverlay(); @@ -88,13 +213,13 @@ card.style.display = 'flex'; card.style.alignItems = 'center'; card.style.gap = '10px'; - card.style.maxWidth = '320px'; + card.style.maxWidth = '360px'; var textBlock = document.createElement('div'); var name = document.createElement('div'); name.style.fontWeight = 'bold'; - name.style.fontSize = '14px'; + name.style.fontSize = '16px'; name.textContent = actor.name; textBlock.appendChild(name); @@ -107,24 +232,27 @@ card.style.pointerEvents = 'auto'; card.style.cursor = 'pointer'; - card.addEventListener('click', function (event) { - event.stopPropagation(); - window.location.hash = '#/details?id=' + actor.jellyfin_id; - }); getPerson(actor.jellyfin_id).then(function (person) { if (!person || !overlayEl || !overlayEl.contains(card)) { return; } + // Clicking the card opens the in-player detail pop-up rather than + // navigating away — this keeps the current playback position. + card.addEventListener('click', function (event) { + event.stopPropagation(); + showDetail(container, actor, person); + }); + if (person.ImageTags && person.ImageTags.Primary) { var img = document.createElement('img'); img.src = window.ApiClient.getImageUrl(actor.jellyfin_id, { type: 'Primary', - maxHeight: 80, + maxHeight: 120, tag: person.ImageTags.Primary }); - img.style.height = '80px'; + img.style.height = '120px'; img.style.width = 'auto'; img.style.borderRadius = '4px'; img.style.objectFit = 'cover';