Add Recently Added in Library sections to Home Screen configuration

This commit is contained in:
HighImKevin
2025-12-07 05:50:19 -05:00
parent 4a5aa3b91d
commit 1fb8733981
4 changed files with 242 additions and 8 deletions
+1
View File
@@ -23,6 +23,7 @@ window.KefinTweaksDefaultConfig = {
"defaultItemLimit": 16,
"defaultSortOrder": "Random",
"defaultCardFormat": "Poster",
"recentlyAddedInLibrary": {},
"recentlyReleased": {
"enabled": true,
"order": 30,
+1
View File
@@ -23,6 +23,7 @@ window.KefinTweaksConfig = {
"defaultItemLimit": 16,
"defaultSortOrder": "Random",
"defaultCardFormat": "Poster",
"recentlyAddedInLibrary": {},
"recentlyReleased": {
"enabled": true,
"order": 30,
+111 -8
View File
@@ -432,7 +432,7 @@
// Build the configuration page content
function buildConfigPageContent(config) {
function buildConfigPageContent(config, libraries = []) {
const scripts = config.scripts || {};
const homeScreen = config.homeScreen || {};
const exclusiveElsewhere = config.exclusiveElsewhere || {};
@@ -509,7 +509,7 @@
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr; gap: 0.5em;">
${buildHomeScreenConfig(homeScreen)}
${buildHomeScreenConfig(homeScreen, libraries)}
</div>
</div>
@@ -1487,11 +1487,11 @@
} = options;
const nameValue = config.name ?? defaultName ?? '';
const itemLimit = config.itemLimit ?? 16;
const itemLimit = config.itemLimit ?? options.defaultItemLimit ?? 16;
const sortOrder = config.sortOrder ?? 'Random';
const sortOrderDirection = config.sortOrderDirection ?? 'Ascending';
const cardFormat = config.cardFormat ?? 'Poster';
const order = config.order ?? 100;
const cardFormat = config.cardFormat ?? options.defaultCardFormat ?? 'Poster';
const order = config.order ?? options.defaultOrder ?? 100;
// IsPlayed logic
// If isPlayed is undefined or null, it means the filter is disabled.
@@ -1638,8 +1638,9 @@
}
// Build home screen configuration
function buildHomeScreenConfig(homeScreen) {
function buildHomeScreenConfig(homeScreen, libraries = []) {
const recentlyReleased = homeScreen.recentlyReleased || {};
const recentlyAddedInLibrary = homeScreen.recentlyAddedInLibrary || {};
const trending = homeScreen.trending || {};
const popularTVNetworks = homeScreen.popularTVNetworks || {};
const watchlist = homeScreen.watchlist || {};
@@ -1707,7 +1708,63 @@
</details>`;
}).join('');
// Build library sections HTML for "Recently Added in Library"
let recentlyAddedInLibraryHtml = '';
if (libraries && libraries.length > 0) {
recentlyAddedInLibraryHtml = libraries.map(library => {
const libConfig = recentlyAddedInLibrary[library.Id] || {};
const prefix = `homeScreen_recentlyAddedInLibrary_${library.Id}`;
const defaultName = `Recently Added in ${library.Name}`;
return `
<details class="listItem" style="display: grid; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 0; gap: 0.5em;">
<summary class="recently-released-subsection-summary" style="display: flex; justify-content: space-between; align-items: center; padding: 0.75em; cursor: pointer; list-style: none; user-select: none;">
<div class="listItemBodyText" style="font-weight: 500; display: flex; align-items: center; gap: 0.5em;">
<span class="material-icons" style="font-size: 1.2em; transition: transform 0.2s;">chevron_right</span>
<span>${library.Name}</span>
<span class="listItemBodyText secondary" style="font-size: 0.9em; font-weight: normal;">(<span class="${prefix}_enabled_display">${libConfig.enabled !== false ? 'Enabled' : 'Disabled'}</span>, Order: <span class="${prefix}_order_display">${libConfig.order || 11}</span>)</span>
</div>
</summary>
<div style="padding: 0 0.75em 0.75em 0.75em; border-top: 1px solid rgba(255,255,255,0.1);">
<div style="margin-top: 0.75em; margin-bottom: 0.75em;">
${buildJellyfinCheckbox(`${prefix}_enabled`, libConfig.enabled !== false, 'Enabled', { 'data-library-id': library.Id, 'class': 'recently-added-library-enabled' })}
</div>
<div class="listItemContent" style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.75em;">
${createSectionConfiguration(prefix, libConfig, {
includeName: true,
defaultName: defaultName,
includeSortOrder: false,
includeOrder: true,
defaultOrder: 11,
includeItemLimit: true,
defaultItemLimit: 30,
defaultCardFormat: library.CollectionType === 'tvshows' ? 'Thumb' : 'Poster'
})}
</div>
</div>
</details>
`;
}).join('');
}
return `
<!-- Recently Added in Library Sections -->
${libraries && libraries.length > 0 ? `
<details style="border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 0.75em; margin-bottom: 1em;">
<summary class="listItemBodyText" style="font-weight: 500; cursor: pointer; margin-bottom: 0.5em; display: flex; align-items: center; gap: 0.5em; border-radius: 0px !important;">
<span class="material-icons" style="font-size: 1.2em; transition: transform 0.2s;">chevron_right</span>
Recently Added in Library Sections
</summary>
<div style="padding: 0.75em 0 0 0;">
<div id="homeScreen_recentlyAddedInLibrary_container">
<div style="display: grid; grid-template-columns: 1fr; gap: 0.75em; margin-bottom: 0.75em;">
${recentlyAddedInLibraryHtml}
</div>
</div>
</div>
</details>
` : ''}
<!-- Recently Released Sections -->
<details style="border: 1px solid rgba(255,255,255,0.1); border-radius: 4px; padding: 0.75em; margin-bottom: 1em;">
<summary class="listItemBodyText" style="font-weight: 500; cursor: pointer; margin-bottom: 0.5em; display: flex; align-items: center; gap: 0.5em; border-radius: 0px !important;">
@@ -2036,6 +2093,29 @@
}
// Open configuration modal
// Fetch libraries (Movies and TV)
async function fetchLibraries() {
if (!window.ApiClient) return [];
try {
// Get user views (libraries)
const result = await window.ApiClient.getUserViews({}, window.ApiClient.getCurrentUserId());
if (result && result.Items) {
// Filter for movies and tvshows
return result.Items.filter(item =>
item.CollectionType === 'movies' || item.CollectionType === 'tvshows'
).map(item => ({
Id: item.Id,
Name: item.Name,
CollectionType: item.CollectionType
}));
}
return [];
} catch (error) {
console.error('[KefinTweaks Configuration] Error fetching libraries:', error);
return [];
}
}
async function openConfigurationModal() {
console.log('[KefinTweaks Configuration] Opening configuration modal...');
@@ -2055,13 +2135,16 @@
currentLoadedConfig = config;
window.KefinTweaksCurrentConfig = config;
// Fetch libraries for configuration
const libraries = await fetchLibraries();
// Build the content (without title, as ModalSystem adds it)
const content = document.createElement('div');
content.className = 'modal-content-wrapper';
content.id = MODAL_ID;
content.innerHTML = `
<div class="kefin-config-wrapper">
${buildConfigPageContent(config)}
${buildConfigPageContent(config, libraries)}
</div>
`;
@@ -5111,7 +5194,27 @@ window.KefinTweaksConfig = ${JSON.stringify(config, null, 2)};`;
...getSectionConfig('homeScreen_recentlyReleased_episodes', { includeName: true, defaultName: 'Recently Aired Episodes', includeSortOrder: true, includeOrder: true, includeIsPlayed: true, includePremiereDays: true })
}
},
trending: {
recentlyAddedInLibrary: (() => {
const libraryConfigs = {};
const libraryCheckboxes = modalInstance.dialogContent.querySelectorAll('.recently-added-library-enabled');
libraryCheckboxes.forEach(checkbox => {
const libraryId = checkbox.getAttribute('data-library-id');
if (libraryId) {
const prefix = `homeScreen_recentlyAddedInLibrary_${libraryId}`;
libraryConfigs[libraryId] = {
enabled: checkbox.checked !== false,
...getSectionConfig(prefix, {
includeName: true,
defaultName: '',
includeSortOrder: false,
includeOrder: true
})
};
}
});
return libraryConfigs;
})(),
trending: {
enabled: document.getElementById('homeScreen_trending_enabled')?.checked === true,
...getSectionConfig('homeScreen_trending', { includeName: true, defaultName: 'Trending' })
},
+129
View File
@@ -777,6 +777,9 @@
// Custom home sections configuration
const customHomeSections = homeScreenConfig.customSections || [];
// Recently Added in Library configuration
const recentlyAddedInLibraryConfig = homeScreenConfig.recentlyAddedInLibrary || {};
// Recently Released sections configuration
const recentlyReleasedConfig = homeScreenConfig.recentlyReleased || {};
const enableNewAndTrending = recentlyReleasedConfig.enabled !== false;
@@ -1429,6 +1432,127 @@
LOG(`Rendered ${successCount}/${sectionsToRender.length} new and trending sections`);
}
/**
* Renders Recently Added in Library sections
* @param {HTMLElement} container - Container to append sections to
*/
async function renderAllRecentlyAddedInLibrarySections(container) {
if (!recentlyAddedInLibraryConfig || Object.keys(recentlyAddedInLibraryConfig).length === 0) {
return;
}
const libraryIds = Object.keys(recentlyAddedInLibraryConfig);
const sectionsToRender = [];
libraryIds.forEach(libraryId => {
const config = recentlyAddedInLibraryConfig[libraryId];
if (config && config.enabled !== false) {
// Check if already rendered
const sectionId = `recently-added-${libraryId}`;
if (!container.querySelector(`[data-custom-section-id="${sectionId}"]`)) {
sectionsToRender.push(renderRecentlyAddedInLibrarySection(libraryId, config, container));
}
}
});
if (sectionsToRender.length === 0) {
return;
}
const results = await Promise.all(sectionsToRender);
const successCount = results.filter(Boolean).length;
LOG(`Rendered ${successCount}/${sectionsToRender.length} recently added library sections`);
}
/**
* Renders a single Recently Added in Library section
* @param {string} libraryId - The library ID
* @param {Object} sectionConfig - Configuration for this section
* @param {HTMLElement} container - Container to append section to
*/
async function renderRecentlyAddedInLibrarySection(libraryId, sectionConfig, container) {
if (sectionConfig.enabled === false) return false;
const userId = ApiClient.getCurrentUserId();
const itemLimit = sectionConfig.itemLimit || 30;
// Determine IncludeItemTypes and viewMoreUrl based on library type
let includeItemTypes = 'Movie,Episode'; // Default fallback
let viewMoreUrl = `#/list.html?parentId=${libraryId}&serverId=${ApiClient.serverId()}`; // Default fallback
try {
const library = await window.ApiClient.getItem(userId, libraryId);
if (library) {
if (library.CollectionType === 'movies') {
includeItemTypes = 'Movie';
viewMoreUrl = `#/movies.html?topParentId=${libraryId}&collectionType=movies&tab=1`;
} else if (library.CollectionType === 'tvshows') {
includeItemTypes = 'Episode';
viewMoreUrl = `#/tv.html?topParentId=${libraryId}&collectionType=tvshows&tab=1`;
}
}
} catch (e) {
WARN(`Failed to fetch library info for ${libraryId}`, e);
}
// Use Users/Items/Latest endpoint logic
// Since apiHelper might not have a dedicated getLatestItems, we construct query
const params = {
Limit: itemLimit,
ParentId: libraryId,
ImageTypeLimit: 1,
EnableImageTypes: 'Primary,Backdrop,Thumb',
IncludeItemTypes: includeItemTypes
};
const queryParams = Object.entries(params)
.map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
.join('&');
const url = `${ApiClient.serverAddress()}/Users/${userId}/Items/Latest?${queryParams}`;
try {
const response = await fetch(url, {
headers: {
'Authorization': `MediaBrowser Token="${ApiClient.accessToken()}"`
}
});
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
const items = await response.json();
if (!items || items.length === 0) return false;
// Render section
if (!window.cardBuilder || !window.cardBuilder.renderCards) {
WARN("cardBuilder.renderCards not available");
return false;
}
const cardContainer = window.cardBuilder.renderCards(
items,
sectionConfig.name || `Recently Added`,
viewMoreUrl,
true, // overflowCard (use standard overflow style for horizontal scroll)
sectionConfig.cardFormat || 'Poster',
false, // sortOrder (already sorted)
'Ascending'
);
const sectionId = `recently-added-${libraryId}`;
cardContainer.setAttribute('data-custom-section-id', sectionId);
cardContainer.setAttribute('data-custom-section-name', sectionConfig.name || `Recently Added`);
cardContainer.style.order = sectionConfig.order || 11;
container.appendChild(cardContainer);
return true;
} catch (err) {
ERR(`Error rendering recently added library section ${libraryId}:`, err);
return false;
}
}
/************ Seasonal Section Helpers ************/
/**
@@ -5810,6 +5934,11 @@
initPromises.push(renderAllCustomSections(homeSectionsContainer));
}
// Add Recently Added in Library sections
if (recentlyAddedInLibraryConfig && Object.keys(recentlyAddedInLibraryConfig).length > 0) {
initPromises.push(renderAllRecentlyAddedInLibrarySections(homeSectionsContainer));
}
// Add new and trending sections if enabled
if (enableNewAndTrending) {
initPromises.push(renderAllNewAndTrendingSections(homeSectionsContainer));