From b4ddc7f638858c16926c5a14b004a0c38189a915 Mon Sep 17 00:00:00 2001 From: HighImKevin Date: Sat, 21 Feb 2026 23:47:58 -0500 Subject: [PATCH] Improve handling for rendering breadcrumbs popover when near edge of viewport --- scripts/breadcrumbNav.css | 27 ++++++++++++- scripts/breadcrumbs.js | 81 +++++++++++++++++++++++++++------------ 2 files changed, 82 insertions(+), 26 deletions(-) diff --git a/scripts/breadcrumbNav.css b/scripts/breadcrumbNav.css index f7f23fb..362daf5 100644 --- a/scripts/breadcrumbNav.css +++ b/scripts/breadcrumbNav.css @@ -32,6 +32,18 @@ top: 100%; left: 0; z-index: 1001; + pointer-events: all; + } + + /* When breadcrumbs are at bottom of page, open popover above */ + .kefinTweaks-popover-container.kefinTweaks-popover-container--above { + top: auto; + bottom: 100%; + } + + .kefinTweaks-popover.kefinTweaks-popover--above { + margin-top: 0; + margin-bottom: 8px; } .kefinTweaks-breadcrumbs span { @@ -50,11 +62,9 @@ /* Popover styles for seasons and albums */ .kefinTweaks-popover { position: relative; - background-color: rgba(29, 38, 53, 0.95); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 4px; padding: 0; - min-width: 200px; max-height: 300px; overflow-y: auto; z-index: 1001; @@ -100,4 +110,17 @@ .headerLeft { overflow: visible; } +} + +.kefinTweaks-popover.itemDetailsGroup:has(.detailsGroupItem:not(.hide)) { + padding: 0 !important; + overflow-y: auto !important; + backdrop-filter: blur(50px); +} +.kefinTweaks-popover.itemDetailsGroup:has(.detailsGroupItem:not(.hide)) .kefinTweaks-popover-item.detailsGroupItem { + padding: .5em 2em; + margin: 0 !important; + border-radius: 0 !important; + width: initial !important; + max-width: initial !important; } \ No newline at end of file diff --git a/scripts/breadcrumbs.js b/scripts/breadcrumbs.js index 4a9ff44..f5747d5 100644 --- a/scripts/breadcrumbs.js +++ b/scripts/breadcrumbs.js @@ -556,7 +556,7 @@ function createPopover(items, currentItem, onItemClick) { const popover = document.createElement('div'); - popover.className = 'kefinTweaks-popover'; + popover.className = 'kefinTweaks-popover itemDetailsGroup'; popover.style.display = 'block'; // Ensure it's visible log('Creating popover with', items.length, 'items'); @@ -565,7 +565,7 @@ items.forEach(item => { const itemElement = document.createElement('div'); - itemElement.className = 'kefinTweaks-popover-item'; + itemElement.className = 'kefinTweaks-popover-item detailsGroupItem'; if (currentItem && item.Id === currentItem.Id) { itemElement.classList.add('selected'); @@ -618,16 +618,31 @@ const popoverContainer = document.getElementById('kefinTweaks-popover-container'); if (popoverContainer) { // Calculate the left offset to align with the trigger element - // Since breadcrumbs are now in the header, we need to calculate relative to the page const triggerRect = triggerElement.getBoundingClientRect(); const popoverContainerRect = popoverContainer.getBoundingClientRect(); - - // Calculate offset from popover container to trigger element const leftOffset = triggerRect.left - popoverContainerRect.left - 22; popover.style.left = `${leftOffset}px`; + // Decide whether to open above or below based on viewport space + // When breadcrumbs are at the bottom of the page, open above + const viewportHeight = window.innerHeight; + const spaceBelow = viewportHeight - triggerRect.bottom; + const spaceAbove = triggerRect.top; + const showAbove = spaceBelow < spaceAbove; + + popoverContainer.classList.toggle('kefinTweaks-popover-container--above', showAbove); + popover.classList.toggle('kefinTweaks-popover--above', showAbove); + // Clear inline top when above so CSS bottom: 100% takes effect; restore when below + if (showAbove) { + popoverContainer.style.top = ''; + popoverContainer.style.bottom = '100%'; + } else { + popoverContainer.style.top = 'calc(100% - 15px)'; + popoverContainer.style.bottom = ''; + } + popoverContainer.appendChild(popover); - log('Popover added to popover container with left offset:', leftOffset); + log('Popover added to popover container with left offset:', leftOffset, 'showAbove:', showAbove); } else { log('Popover container not found, falling back to trigger element'); triggerElement.style.position = 'relative'; @@ -656,9 +671,15 @@ } const url = ApiClient.getUrl(`Users/${currentUserId}/Items/${itemId}`); - const response = await ApiClient.getJSON(url); - log('Retrieved item details:', response.Name, response.Type); - return response; + const response = await window.apiHelper.getQuery(url, { useCache: true }); + + if (!response.data) { + response.data = await response.dataPromise; + } + + const item = response.data; + log('Retrieved item details:', item.Name, item.Type); + return item; } catch (err) { console.error('Failed to get item details:', err); return null; @@ -667,13 +688,14 @@ async function getSeasons(seriesId) { try { - const url = ApiClient.getUrl(`Shows/${seriesId}/Seasons`, { - userId: ApiClient.getCurrentUserId(), - imageTypeLimit: 1 - }); - const response = await ApiClient.getJSON(url); - log('Retrieved seasons:', response.Items?.length || 0); - return response.Items || []; + const url = ApiClient.getUrl(`Shows/${seriesId}/Seasons`); + const response = await window.apiHelper.getQuery(url, { useCache: true }); + if (!response.data) { + response.data = await response.dataPromise; + } + const seasons = response.data.Items || response.data || []; + log('Retrieved seasons:', seasons.length); + return seasons; } catch (err) { error('Failed to get seasons:', err); return []; @@ -693,9 +715,13 @@ AlbumArtistIds: artistId, SortBy: 'PremiereDate,ProductionYear,Sortname' }); - const response = await ApiClient.getJSON(url); - log('Retrieved albums:', response.Items?.length || 0); - return response.Items || []; + const response = await window.apiHelper.getQuery(url, { useCache: true }); + if (!response.data) { + response.data = await response.dataPromise; + } + const albums = response.data.Items || response.data || []; + log('Retrieved albums:', albums.length); + return albums; } catch (err) { error('Failed to get albums:', err); return []; @@ -716,8 +742,11 @@ AlbumArtistIds: idsParam, SortBy: 'PremiereDate,ProductionYear,Sortname' }); - const response = await ApiClient.getJSON(url); - const items = response.Items || []; + const response = await window.apiHelper.getQuery(url, { useCache: true }); + if (!response.data) { + response.data = await response.dataPromise; + } + const items = response.data.Items || response.data || []; // Deduplicate by Id when multiple artists overlap const unique = []; const seen = new Set(); @@ -746,9 +775,13 @@ StartIndex: 0, SortBy: 'IndexNumber,SortName' }); - const response = await ApiClient.getJSON(url); - log('Retrieved songs:', response.Items?.length || 0); - return response.Items || []; + const response = await window.apiHelper.getQuery(url, { useCache: true }); + if (!response.data) { + response.data = await response.dataPromise; + } + const songs = response.data.Items || response.data || []; + log('Retrieved songs:', songs.length); + return songs; } catch (err) { error('Failed to get songs:', err); return [];