Improve handling of Flatten Series Episodes and Section Configure popover

This commit is contained in:
HighImKevin committed 2026-10-06 04:44:36 -04:00
1 parent a42d7a44ea
commit 0c11842583
2 files changed
+89 -53

No files matched your search

+62 -34
View File
@@ -784,21 +784,23 @@
function flattenSeriesEpisodes(items) {
if (!items || items.length === 0) return items;
// Keep only the first item in the array for any given series
const seriesMap = new Map();
items.forEach((item) => {
if (item.Type !== 'Episode') {
return;
const seenSeries = new Set();
const result = [];
for (const item of items) {
if (item.Type === 'Episode') {
// Only keep the first episode seen for a given SeriesId
if (item.SeriesId && !seenSeries.has(item.SeriesId)) {
seenSeries.add(item.SeriesId);
result.push(item);
}
} else {
// Keep non-Episode items as-is, in order
result.push(item);
}
}
if (seriesMap.has(item.SeriesId)) {
return;
}
seriesMap.set(item.SeriesId, item);
});
return Array.from(seriesMap.values());
return result;
}
function formatAirDate(premiereDate) {
@@ -1665,26 +1667,35 @@
}
let activePopover = null;
let activePopoverCloseHandler = null;
const closePopover = () => {
if (activePopoverCloseHandler) {
document.removeEventListener('click', activePopoverCloseHandler);
activePopoverCloseHandler = null;
}
if (activePopover) {
activePopover.remove();
activePopover = null;
}
};
if (!document.getElementById('kefinTweaks-multiQueryPopover-style')) {
const styleElement = document.createElement('style');
styleElement.id = 'kefinTweaks-multiQueryPopover-style';
styleElement.textContent = `
const multiQueryPopoverCss = `
.multiQueryPopover {
background-color: rgba(0, 0, 0, 0.95);
position: absolute !important;
z-index: 1001;
display: block;
overflow-y: auto;
box-sizing: border-box;
}
`;
document.head.appendChild(styleElement);
let multiQueryStyleEl = document.getElementById('kefinTweaks-multiQueryPopover-style');
if (!multiQueryStyleEl) {
multiQueryStyleEl = document.createElement('style');
multiQueryStyleEl.id = 'kefinTweaks-multiQueryPopover-style';
document.head.appendChild(multiQueryStyleEl);
}
multiQueryStyleEl.textContent = multiQueryPopoverCss;
selectButton.addEventListener('click', (e) => {
e.stopPropagation();
@@ -1763,15 +1774,37 @@
popover.appendChild(itemElement);
});
titleContainer.style.position = 'relative';
selectButton.parentNode.insertBefore(popover, selectButton.nextSibling);
// Mount on body with absolute + document coords so the menu scrolls with the button
document.body.appendChild(popover);
const margin = 8;
const edgeMargin = 16;
const gap = 8;
const scrollX = window.scrollX || window.pageXOffset || 0;
const scrollY = window.scrollY || window.pageYOffset || 0;
const buttonRect = selectButton.getBoundingClientRect();
const containerRect = titleContainer.getBoundingClientRect();
popover.style.left = `${Math.max(0, buttonRect.left - containerRect.left)}px`;
popover.style.top = '100%';
const popoverWidth = popover.offsetWidth || popover.getBoundingClientRect().width || 200;
const spaceBelow = window.innerHeight - buttonRect.bottom - margin;
const spaceAbove = buttonRect.top - margin;
const placeBelow = spaceBelow >= Math.min(spaceAbove, 160) || spaceBelow >= spaceAbove;
const available = placeBelow ? spaceBelow : spaceAbove;
const maxHeight = Math.max(120, Math.min(window.innerHeight * 0.6, available - gap));
let leftViewport = buttonRect.left;
leftViewport = Math.min(
Math.max(edgeMargin, leftViewport),
Math.max(edgeMargin, window.innerWidth - popoverWidth - edgeMargin),
);
popover.style.position = 'absolute';
popover.style.left = `${leftViewport + scrollX}px`;
popover.style.maxHeight = `${maxHeight}px`;
popover.style.bottom = 'auto';
popover.style.marginTop = '8px';
popover.style.marginBottom = '0';
if (placeBelow) {
popover.style.top = `${buttonRect.bottom + gap + scrollY}px`;
} else {
const height = Math.min(popover.offsetHeight || maxHeight, maxHeight);
popover.style.top = `${Math.max(scrollY + margin, buttonRect.top - gap - height + scrollY)}px`;
}
activePopover = popover;
if (selectedItemElement) {
@@ -1784,14 +1817,13 @@
}, 10);
}
const closeHandler = (ev) => {
activePopoverCloseHandler = (ev) => {
if (!popover.contains(ev.target) && !selectButton.contains(ev.target)) {
closePopover();
document.removeEventListener('click', closeHandler);
}
};
setTimeout(() => {
document.addEventListener('click', closeHandler);
document.addEventListener('click', activePopoverCloseHandler);
}, 100);
});
}
@@ -3669,12 +3701,8 @@
imageUrl = `${serverAddress}/Items/${itemId}/Images/Logo?${imageParams}&quality=96&tag=${logoTag}`;
} else if (cardFormat === 'clear art' && item.ImageTags?.Art) {
imageUrl = `${serverAddress}/Items/${item.Id}/Images/Art?${imageParams}&quality=96&tag=${item.ImageTags.Art}`;
} else if (cardFormat === 'banner') {
if (item.ImageTags?.Banner) {
imageUrl = `${serverAddress}/Items/${item.Id}/Images/Banner?${imageParams}&quality=96&tag=${item.ImageTags.Banner}`;
} else {
imageUrl = `${serverAddress}/Items/${item.SeriesId || item.ParentId}/Images/Banner?${imageParams}&quality=96`;
}
} else if (cardFormat === 'banner' && item.ImageTags?.Banner) {
imageUrl = `${serverAddress}/Items/${item.Id}/Images/Banner?${imageParams}&quality=96&tag=${item.ImageTags.Banner}`;
} else if (cardFormat === 'disc' && item.ImageTags?.Disc) {
imageUrl = `${serverAddress}/Items/${item.Id}/Images/Disc?${imageParams}&quality=96&tag=${item.ImageTags.Disc}`;
} else if (item.ImageTags?.Primary) {
+27 -19
View File
@@ -1008,7 +1008,7 @@
if (activePopover.popover.parentElement !== mountElement) {
mountElement.appendChild(activePopover.popover);
}
requestAnimationFrame(() => repositionActivePopover());
requestAnimationFrame(() => repositionActivePopover({ force: true }));
}
}
@@ -1868,6 +1868,7 @@
flipItems.forEach(({ el }) => clearSectionFlipStyles(el));
clearOverflowAnchorNone(anchorEls || []);
if (activePopover) activePopover.isAnimating = false;
repositionActivePopover({ force: true });
resolve();
};
@@ -1880,6 +1881,7 @@
el.style.transform = `translateY(${invertY * (1 - eased)}px)`;
});
setScrollY(scrollRoot, startScroll + scrollDelta * eased);
repositionActivePopover({ force: true });
if (t < 1) requestAnimationFrame(frame);
else finish();
@@ -1921,6 +1923,7 @@
setScrollY(scrollRoot, getScrollY(scrollRoot) - delta);
void currentEl.offsetHeight;
clearOverflowAnchorNone(anchorEls);
repositionActivePopover({ force: true });
return;
}
@@ -1981,6 +1984,7 @@
setScrollY(scrollRoot, getScrollY(scrollRoot) - delta);
void currentEl.offsetHeight;
clearOverflowAnchorNone(anchorEls);
repositionActivePopover({ force: true });
return;
}
@@ -2103,21 +2107,22 @@
sectionElement?.classList.remove('kefin-section-configure-popover-open');
}
function repositionActivePopover() {
if (!activePopover || activePopover.isAnimating) return;
// Once placed, never move — scrolling/resize/reorder must not shift targets mid-click.
if (activePopover.placementLocked) return;
function repositionActivePopover({ force = false } = {}) {
if (!activePopover || (activePopover.isAnimating && !force)) return;
// Once placed, never move on scroll/resize — reorder/remount can force a re-anchor.
if (activePopover.placementLocked && !force) return;
const anchor = activePopover.anchorButton || getConfigureAnchorButton(activePopover.entry);
const mount = activePopover.mountElement
|| resolvePopoverMount(anchor, activePopover.entry?.element);
if (!anchor || !mount || !document.body.contains(anchor) || !document.body.contains(mount)) return;
activePopover.anchorButton = anchor;
activePopover.anchorButton = getConfigureAnchorButton(activePopover.entry) || anchor;
activePopover.mountElement = mount;
if (activePopover.entry?.element) {
activePopover.entry.element = anchor.closest('[data-section-id]') || activePopover.entry.element;
activePopover.entry.element = activePopover.anchorButton?.closest('[data-section-id]')
|| activePopover.entry.element;
activePopover.sectionElement = activePopover.entry.element;
}
positionConfigurePopover(activePopover.popover, anchor, mount);
positionConfigurePopover(activePopover.popover, activePopover.anchorButton, mount);
activePopover.placementLocked = true;
if (activePopover.formatMenu && activePopover.formatMenuAnchor) {
positionFormatMenu(activePopover.formatMenu, activePopover.formatMenuAnchor);
@@ -2273,18 +2278,19 @@
const buttonRect = anchorButton.getBoundingClientRect();
const popoverWidth = popover.offsetWidth || popover.getBoundingClientRect().width || 280;
const margin = 8;
const edgeMargin = 16;
const gap = 8;
let leftViewport = buttonRect.left + (buttonRect.width / 2) - (popoverWidth / 2);
const minLeft = Math.max(sectionRect.left + margin, margin);
const maxLeft = Math.min(sectionRect.right - popoverWidth - margin, window.innerWidth - popoverWidth - margin);
const minLeft = Math.max(sectionRect.left + margin, edgeMargin);
const maxLeft = Math.min(sectionRect.right - popoverWidth - margin, window.innerWidth - popoverWidth - edgeMargin);
if (maxLeft >= minLeft) {
leftViewport = Math.min(Math.max(leftViewport, minLeft), maxLeft);
} else {
leftViewport = Math.min(
Math.max(sectionRect.left + margin, margin),
Math.max(margin, window.innerWidth - popoverWidth - margin)
Math.max(sectionRect.left + margin, edgeMargin),
Math.max(edgeMargin, window.innerWidth - popoverWidth - edgeMargin)
);
}
@@ -2300,16 +2306,18 @@
if (activePopover) activePopover.anchorMode = placeBelow ? 'below' : 'above';
}
const scrollX = window.scrollX || window.pageXOffset || 0;
const scrollY = window.scrollY || window.pageYOffset || 0;
popover.classList.toggle('is-below', placeBelow);
popover.style.position = 'fixed';
popover.style.left = `${leftViewport}px`;
popover.style.position = 'absolute';
popover.style.left = `${leftViewport + scrollX}px`;
popover.style.bottom = 'auto';
if (placeBelow) {
popover.style.top = `${buttonRect.bottom + gap}px`;
popover.style.bottom = 'auto';
popover.style.top = `${buttonRect.bottom + gap + scrollY}px`;
} else {
const height = getPopoverPlacementHeight(popover, popover.classList.contains('is-expanded'));
popover.style.top = `${Math.max(margin, buttonRect.top - gap - height)}px`;
popover.style.bottom = 'auto';
popover.style.top = `${Math.max(scrollY + margin, buttonRect.top - gap - height + scrollY)}px`;
}
}
@@ -2869,7 +2877,7 @@
overflow: visible;
}
.kefin-section-configure-popover {
position: fixed;
position: absolute;
z-index: 12000;
box-sizing: border-box;
display: flex;