mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 15:52:47 +01:00
Update sections in place when editing section server defaults from the home screen
This commit is contained in:
1 parent
a3094caf70
commit
a27bd54072
2 files changed
+213
-31
No files matched your search
+52
-31
@@ -2746,6 +2746,8 @@
|
||||
getActiveViewMoreUrl: getActiveViewMoreUrl,
|
||||
refreshSectionQueries: refreshSectionQueries,
|
||||
loadActiveSectionQueryItems: loadActiveSectionQueryItems,
|
||||
createSectionScrollableContainer: createSectionScrollableContainer,
|
||||
applySectionPresentationAttrs: applySectionPresentationAttrs,
|
||||
attachSectionControlButtons: attachSectionControlButtons,
|
||||
ensureCardBorders: ensureCardBorders,
|
||||
applyItemsLayoutState: applyItemsLayoutState,
|
||||
@@ -4464,11 +4466,26 @@
|
||||
|
||||
// Overlays: desktop = hover FAB + br actions; mobile = native bottom-right play only
|
||||
const NON_PLAYABLE_TYPES = ['CollectionFolder', 'Folder', 'UserView', 'Virtual', 'Studio', 'Genre', 'Person'];
|
||||
const NO_RATE_TYPES = ['UserView', 'CollectionFolder', 'Folder', 'Program', 'Timer', 'SeriesTimer', 'Channel'];
|
||||
const canShowPlayOverlay =
|
||||
!isCustomCard
|
||||
&& !NON_PLAYABLE_TYPES.includes(item.Type)
|
||||
&& item.LocationType !== 'Virtual'
|
||||
&& item.Type !== 'Person';
|
||||
// Match jellyfin-web itemHelper.canMarkPlayed (cards: no Folder/CollectionFolder/UserView)
|
||||
const canMarkPlayed =
|
||||
item.Type !== 'Program'
|
||||
&& (
|
||||
(item.MediaType === 'Video' && item.Type !== 'TvChannel')
|
||||
|| (item.MediaType === 'Audio' && item.Type === 'AudioBook')
|
||||
|| item.Type === 'Series'
|
||||
|| item.Type === 'Season'
|
||||
|| item.Type === 'BoxSet'
|
||||
|| item.MediaType === 'Book'
|
||||
)
|
||||
&& !NON_PLAYABLE_TYPES.includes(item.Type);
|
||||
// Match jellyfin-web itemHelper.canRate type denylist (do not gate on missing UserData)
|
||||
const canRate = !NO_RATE_TYPES.includes(item.Type);
|
||||
|
||||
let cardOverlayContainer = null;
|
||||
let mobileOverlayPlayButton = null;
|
||||
@@ -4535,40 +4552,44 @@
|
||||
buttonContainer.appendChild(watchlistButton);
|
||||
}
|
||||
|
||||
const watchedButton = document.createElement('button');
|
||||
watchedButton.setAttribute('is', 'emby-playstatebutton');
|
||||
watchedButton.type = 'button';
|
||||
watchedButton.setAttribute('data-action', 'none');
|
||||
watchedButton.className = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light emby-button';
|
||||
watchedButton.setAttribute('data-id', itemId);
|
||||
watchedButton.setAttribute('data-serverid', serverId);
|
||||
watchedButton.setAttribute('data-itemtype', itemType);
|
||||
watchedButton.setAttribute('data-played', item.UserData?.Played || 'false');
|
||||
watchedButton.title = 'Mark played';
|
||||
if (canMarkPlayed) {
|
||||
const watchedButton = document.createElement('button');
|
||||
watchedButton.setAttribute('is', 'emby-playstatebutton');
|
||||
watchedButton.type = 'button';
|
||||
watchedButton.setAttribute('data-action', 'none');
|
||||
watchedButton.className = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light emby-button';
|
||||
watchedButton.setAttribute('data-id', itemId);
|
||||
watchedButton.setAttribute('data-serverid', serverId);
|
||||
watchedButton.setAttribute('data-itemtype', itemType);
|
||||
watchedButton.setAttribute('data-played', item.UserData?.Played || 'false');
|
||||
watchedButton.title = 'Mark played';
|
||||
|
||||
const watchedIcon = document.createElement('span');
|
||||
watchedIcon.className = 'material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check playstatebutton-icon-unplayed';
|
||||
watchedIcon.setAttribute('aria-hidden', 'true');
|
||||
watchedButton.appendChild(watchedIcon);
|
||||
buttonContainer.appendChild(watchedButton);
|
||||
const watchedIcon = document.createElement('span');
|
||||
watchedIcon.className = 'material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover check playstatebutton-icon-unplayed';
|
||||
watchedIcon.setAttribute('aria-hidden', 'true');
|
||||
watchedButton.appendChild(watchedIcon);
|
||||
buttonContainer.appendChild(watchedButton);
|
||||
}
|
||||
|
||||
const favoriteButton = document.createElement('button');
|
||||
favoriteButton.setAttribute('is', 'emby-ratingbutton');
|
||||
favoriteButton.type = 'button';
|
||||
favoriteButton.setAttribute('data-action', 'none');
|
||||
favoriteButton.className = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light emby-button';
|
||||
favoriteButton.setAttribute('data-id', itemId);
|
||||
favoriteButton.setAttribute('data-serverid', serverId);
|
||||
favoriteButton.setAttribute('data-itemtype', itemType);
|
||||
favoriteButton.setAttribute('data-likes', '');
|
||||
favoriteButton.setAttribute('data-isfavorite', item.UserData?.IsFavorite || 'false');
|
||||
favoriteButton.title = 'Add to favorites';
|
||||
if (canRate) {
|
||||
const favoriteButton = document.createElement('button');
|
||||
favoriteButton.setAttribute('is', 'emby-ratingbutton');
|
||||
favoriteButton.type = 'button';
|
||||
favoriteButton.setAttribute('data-action', 'none');
|
||||
favoriteButton.className = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light emby-button';
|
||||
favoriteButton.setAttribute('data-id', itemId);
|
||||
favoriteButton.setAttribute('data-serverid', serverId);
|
||||
favoriteButton.setAttribute('data-itemtype', itemType);
|
||||
favoriteButton.setAttribute('data-likes', '');
|
||||
favoriteButton.setAttribute('data-isfavorite', item.UserData?.IsFavorite || 'false');
|
||||
favoriteButton.title = 'Add to favorites';
|
||||
|
||||
const favoriteIcon = document.createElement('span');
|
||||
favoriteIcon.className = 'material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite';
|
||||
favoriteIcon.setAttribute('aria-hidden', 'true');
|
||||
favoriteButton.appendChild(favoriteIcon);
|
||||
buttonContainer.appendChild(favoriteButton);
|
||||
const favoriteIcon = document.createElement('span');
|
||||
favoriteIcon.className = 'material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover favorite';
|
||||
favoriteIcon.setAttribute('aria-hidden', 'true');
|
||||
favoriteButton.appendChild(favoriteIcon);
|
||||
buttonContainer.appendChild(favoriteButton);
|
||||
}
|
||||
|
||||
const moreButton = document.createElement('button');
|
||||
moreButton.className = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light';
|
||||
|
||||
@@ -1957,6 +1957,166 @@
|
||||
return enqueueHomeViewJob({ type: 'home', hash: window.location.hash || '' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Full re-render + replace a single mounted home section (admin editor save).
|
||||
* If the section is not mounted, no-ops (does not refresh the whole home).
|
||||
* @param {string} sectionId
|
||||
* @param {Object} [sectionConfig] saved section config from the editor
|
||||
* @returns {Promise<boolean>} true if replaced
|
||||
*/
|
||||
async function refreshHomeSectionById(sectionId, sectionConfig) {
|
||||
if (!sectionId) return false;
|
||||
|
||||
invalidateHomeScreenConfig('refreshHomeSectionById');
|
||||
|
||||
const escaped = typeof CSS !== 'undefined' && CSS.escape
|
||||
? CSS.escape(String(sectionId))
|
||||
: String(sectionId).replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
const sectionElement =
|
||||
document.querySelector(`.homePage:not(.hide) #homeTab .sections [data-section-id="${escaped}"]`)
|
||||
|| document.querySelector(`[data-section-id="${escaped}"]`);
|
||||
if (!sectionElement?.isConnected) {
|
||||
LOG('refreshHomeSectionById: section not mounted', sectionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
const runtime = window.KefinHomeScreenSectionRuntime?.get?.(sectionId);
|
||||
const cfg = sectionConfig
|
||||
? { ...(runtime?.effectiveConfig || {}), ...sectionConfig }
|
||||
: (runtime?.effectiveConfig || null);
|
||||
if (!cfg) {
|
||||
WARN('refreshHomeSectionById: no section config for', sectionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
const cardBuilder = window.cardBuilder;
|
||||
if (!cardBuilder?.createSectionScrollableContainer || !cardBuilder?.renderSpotlightSection) {
|
||||
WARN('refreshHomeSectionById: cardBuilder rebuild APIs unavailable');
|
||||
return false;
|
||||
}
|
||||
|
||||
let items = [];
|
||||
try {
|
||||
if (Array.isArray(cfg.items) && cfg.items.length > 0) {
|
||||
items = cardBuilder.postProcessItems
|
||||
? cardBuilder.postProcessItems(cfg, cfg.items)
|
||||
: cfg.items.slice();
|
||||
} else if (window.sectionHelper?.loadSectionForRendering) {
|
||||
const loaded = await window.sectionHelper.loadSectionForRendering(cfg);
|
||||
const result = loaded?.result;
|
||||
const raw = typeof result?.ensureData === 'function'
|
||||
? await result.ensureData()
|
||||
: await (result?.dataPromise ?? result?.data);
|
||||
if (Array.isArray(raw)) items = raw;
|
||||
else if (Array.isArray(raw?.Items)) items = raw.Items;
|
||||
else items = [];
|
||||
} else if (cardBuilder.refreshSectionQueries) {
|
||||
items = await cardBuilder.refreshSectionQueries(cfg);
|
||||
}
|
||||
} catch (e) {
|
||||
ERR('refreshHomeSectionById: failed to load items', sectionId, e);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!items.length) {
|
||||
WARN('refreshHomeSectionById: no items after load', sectionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
const title = cardBuilder.getSectionTitleForMultiQuery?.(cfg) || cfg.name || '';
|
||||
const viewMoreUrl = cardBuilder.getActiveViewMoreUrl?.(cfg) || cfg.viewMoreUrl || null;
|
||||
const isSpotlight = cfg.renderMode === 'Spotlight' || cfg.spotlight === true;
|
||||
|
||||
let newContent = null;
|
||||
if (isSpotlight) {
|
||||
newContent = cardBuilder.renderSpotlightSection(items, title, {
|
||||
viewMoreUrl,
|
||||
...(cfg.spotlightConfig || {}),
|
||||
});
|
||||
} else {
|
||||
let cardFormat = cfg.cardFormat;
|
||||
if (cardFormat === 'random' || cardFormat === 'Random') {
|
||||
cardFormat = cardBuilder.getRandomCardFormat?.() || 'Poster';
|
||||
}
|
||||
newContent = cardBuilder.createSectionScrollableContainer(
|
||||
items,
|
||||
title,
|
||||
viewMoreUrl,
|
||||
cfg.overflowCard !== false,
|
||||
cardFormat,
|
||||
cfg,
|
||||
);
|
||||
}
|
||||
|
||||
if (!newContent) {
|
||||
WARN('refreshHomeSectionById: failed to build section DOM', sectionId);
|
||||
return false;
|
||||
}
|
||||
|
||||
newContent.setAttribute('data-section-id', cfg.id);
|
||||
if (cfg.order != null) {
|
||||
newContent.dataset.order = String(cfg.order);
|
||||
newContent.style.order = cfg.order;
|
||||
}
|
||||
if (cfg.cardFormat) {
|
||||
newContent.setAttribute('data-card-format', cfg.cardFormat);
|
||||
}
|
||||
|
||||
// Preserve non-mode layout classes from the previous root (e.g. category filters)
|
||||
const SPOTLIGHT_MODE_CLASSES = ['spotlight-section', 'padded-left'];
|
||||
const NORMAL_MODE_CLASSES = ['verticalSection', 'emby-scroller-container', 'custom-scroller-container'];
|
||||
const wasSpotlight = sectionElement.classList.contains('spotlight-section');
|
||||
const modeChanged = wasSpotlight !== isSpotlight;
|
||||
if (modeChanged) {
|
||||
const stale = wasSpotlight ? SPOTLIGHT_MODE_CLASSES : NORMAL_MODE_CLASSES;
|
||||
sectionElement.classList.forEach((cls) => {
|
||||
if (!stale.includes(cls)) newContent.classList.add(cls);
|
||||
});
|
||||
}
|
||||
|
||||
if (typeof cardBuilder.applySectionPresentationAttrs === 'function') {
|
||||
cardBuilder.applySectionPresentationAttrs(newContent, cfg);
|
||||
}
|
||||
|
||||
const layout = cardBuilder.resolveItemsLayout?.(cfg) || 'row';
|
||||
const gapless = cardBuilder.resolveUseGaplessCards?.(cfg) === true;
|
||||
|
||||
sectionElement.replaceWith(newContent);
|
||||
|
||||
if (!isSpotlight && cardBuilder.applyItemsLayoutState) {
|
||||
cardBuilder.applyItemsLayoutState(newContent, layout, gapless);
|
||||
}
|
||||
if (cardBuilder.ensureCardBorders) {
|
||||
cardBuilder.ensureCardBorders(newContent);
|
||||
}
|
||||
if (cardBuilder.attachSectionControlButtons) {
|
||||
cardBuilder.attachSectionControlButtons(cfg, newContent, items);
|
||||
}
|
||||
if (cardBuilder.registerLazyImagesInSection) {
|
||||
cardBuilder.registerLazyImagesInSection(newContent);
|
||||
} else if (cardBuilder.registerLazyImagesIn) {
|
||||
cardBuilder.registerLazyImagesIn(newContent);
|
||||
}
|
||||
|
||||
if (runtime) {
|
||||
runtime.element = newContent;
|
||||
runtime.effectiveConfig = cfg;
|
||||
runtime.cachedItems = items;
|
||||
window.KefinHomeScreenSectionRuntime?.register?.(runtime);
|
||||
} else if (window.KefinHomeScreenSectionRuntime?.register) {
|
||||
window.KefinHomeScreenSectionRuntime.register({
|
||||
sectionId: cfg.id,
|
||||
element: newContent,
|
||||
effectiveConfig: cfg,
|
||||
cachedItems: items,
|
||||
serverDefaults: {},
|
||||
});
|
||||
}
|
||||
|
||||
LOG('refreshHomeSectionById: replaced section', sectionId);
|
||||
return true;
|
||||
}
|
||||
|
||||
const HOME_CHROME_CREATE_ID = 'kefin-home-create-section-btn';
|
||||
const HOME_CHROME_CATEGORY_RAIL_ID = 'kefin-home-category-rail';
|
||||
const HOME_CHROME_CATEGORY_STYLE_ID = 'kefin-home-category-filter-styles';
|
||||
@@ -2731,6 +2891,7 @@
|
||||
window.homeScreen3 = {
|
||||
init: () => enqueueHomeViewJob({ type: 'home', hash: window.location.hash || '' }),
|
||||
refreshHomeSections,
|
||||
refreshHomeSectionById,
|
||||
resolveDiscoverySection: buildDiscoverySectionInstance,
|
||||
sectionHelper: () => window.sectionHelper,
|
||||
getActiveHomeCategory: () => homeChromeActiveCategory || 'none',
|
||||
|
||||
Reference in new issue
Block a user