mirror of
https://github.com/ranaldsgift/KefinTweaks.git
synced 2026-11-05 15:52:47 +01:00
309 lines
14 KiB
JavaScript
309 lines
14 KiB
JavaScript
// Jellyfin Card Builder
|
|
// This module provides a main entry point function to build Jellyfin cards
|
|
// Usage: window.cardBuilder.buildCard(jellyfinItem)
|
|
|
|
(function() {
|
|
'use strict';
|
|
|
|
// Main card builder object
|
|
const cardBuilder = {
|
|
/**
|
|
* Main entry point function to build a Jellyfin card
|
|
* @param {Object} item - The Jellyfin item object
|
|
* @returns {HTMLElement} - The constructed card element
|
|
*/
|
|
buildCard: function(item) {
|
|
return createJellyfinCardElement(item);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Creates a Jellyfin card element from an item
|
|
* @param {Object} item - The Jellyfin item object
|
|
* @returns {HTMLElement} - The constructed card element
|
|
*/
|
|
function createJellyfinCardElement(item) {
|
|
const serverId = ApiClient.serverId();
|
|
const serverAddress = ApiClient.serverAddress();
|
|
|
|
// Determine card type based on item type
|
|
let cardClass, padderClass, imageParams;
|
|
if (item.Type === 'Episode' || item.Type === 'TvChannel') {
|
|
cardClass = 'overflowBackdropCard';
|
|
padderClass = 'cardPadder-overflowBackdrop';
|
|
imageParams = 'fillHeight=267&fillWidth=474';
|
|
} else if (['MusicAlbum', 'Audio', 'Artist', 'MusicArtist'].includes(item.Type)) {
|
|
cardClass = 'overflowSquareCard';
|
|
padderClass = 'cardPadder-overflowSquare';
|
|
imageParams = 'fillHeight=297&fillWidth=297';
|
|
} else {
|
|
// Default poster style for Movies, Series, etc.
|
|
cardClass = 'overflowPortraitCard';
|
|
padderClass = 'cardPadder-overflowPortrait';
|
|
imageParams = 'fillHeight=446&fillWidth=297';
|
|
}
|
|
|
|
// Create the main card container
|
|
const card = document.createElement('div');
|
|
card.className = `card ${cardClass} card-hoverable card-withuserdata`;
|
|
card.setAttribute('data-index', '0');
|
|
card.setAttribute('data-isfolder', item.Type === 'MusicAlbum' || item.Type === 'Artist' ? 'true' : 'false');
|
|
card.setAttribute('data-serverid', serverId);
|
|
card.setAttribute('data-id', item.Id);
|
|
card.setAttribute('data-type', item.Type);
|
|
card.setAttribute('data-mediatype', item.MediaType || 'Video');
|
|
card.setAttribute('data-prefix', item.Name?.startsWith('The ') ? 'THE' : '');
|
|
|
|
// Card box container
|
|
const cardBox = document.createElement('div');
|
|
cardBox.className = 'cardBox cardBox-bottompadded';
|
|
|
|
// Card scalable container
|
|
const cardScalable = document.createElement('div');
|
|
cardScalable.className = 'cardScalable';
|
|
|
|
// Card padder with icon
|
|
const cardPadder = document.createElement('div');
|
|
cardPadder.className = `cardPadder ${padderClass} lazy-hidden-children`;
|
|
|
|
const cardIcon = document.createElement('span');
|
|
cardIcon.className = 'cardImageIcon material-icons';
|
|
cardIcon.setAttribute('aria-hidden', 'true');
|
|
|
|
// Set icon based on item type
|
|
if (item.Type === 'Movie') {
|
|
cardIcon.textContent = 'movie';
|
|
} else if (item.Type === 'Series') {
|
|
cardIcon.textContent = 'tv';
|
|
} else if (item.Type === 'Episode') {
|
|
cardIcon.textContent = 'play_circle';
|
|
} else if (item.Type === 'MusicAlbum') {
|
|
cardIcon.textContent = 'album';
|
|
} else if (item.Type === 'Audio') {
|
|
cardIcon.textContent = 'music_note';
|
|
} else if (item.Type === 'Artist') {
|
|
cardIcon.textContent = 'person';
|
|
} else {
|
|
cardIcon.textContent = 'folder';
|
|
}
|
|
|
|
cardPadder.appendChild(cardIcon);
|
|
|
|
// Blurhash canvas (placeholder)
|
|
const blurhashCanvas = document.createElement('canvas');
|
|
blurhashCanvas.setAttribute('aria-hidden', 'true');
|
|
blurhashCanvas.width = 20;
|
|
blurhashCanvas.height = 20;
|
|
blurhashCanvas.className = 'blurhash-canvas lazy-hidden';
|
|
|
|
// Card image container
|
|
const cardImageContainer = document.createElement('a');
|
|
cardImageContainer.href = `#/details?id=${item.Id}&serverId=${serverId}`;
|
|
cardImageContainer.className = 'cardImageContainer coveredImage cardContent itemAction lazy blurhashed lazy-image-fadein-fast';
|
|
cardImageContainer.setAttribute('data-action', 'link');
|
|
cardImageContainer.setAttribute('aria-label', item.Name || 'Unknown');
|
|
|
|
// Set background image or icon
|
|
if (item.ImageTags?.Primary) {
|
|
let imageUrl;
|
|
if (item.IsJellyseerr) {
|
|
// Jellyseerr items use external image URLs
|
|
imageUrl = item.ImageTags.Primary.startsWith('http')
|
|
? item.ImageTags.Primary
|
|
: `https://image.tmdb.org/t/p/w500${item.ImageTags.Primary}`;
|
|
} else {
|
|
// Regular Jellyfin items
|
|
imageUrl = `${serverAddress}/Items/${item.Id}/Images/Primary?${imageParams}&quality=96&tag=${item.ImageTags.Primary}`;
|
|
}
|
|
cardImageContainer.style.backgroundImage = `url("${imageUrl}")`;
|
|
} else {
|
|
// No image - add icon as inner element
|
|
const iconSpan = document.createElement('span');
|
|
iconSpan.className = 'cardImageIcon material-icons';
|
|
iconSpan.setAttribute('aria-hidden', 'true');
|
|
|
|
// Set icon based on item type
|
|
if (item.Type === 'Movie') {
|
|
iconSpan.textContent = 'movie';
|
|
} else if (item.Type === 'Series') {
|
|
iconSpan.textContent = 'tv';
|
|
} else if (item.Type === 'Episode') {
|
|
iconSpan.textContent = 'tv';
|
|
} else if (item.Type === 'Person') {
|
|
iconSpan.textContent = 'person';
|
|
} else if (item.Type === 'MusicAlbum') {
|
|
iconSpan.textContent = 'album';
|
|
} else if (item.Type === 'Audio') {
|
|
iconSpan.textContent = 'music_note';
|
|
} else if (item.Type === 'Artist' || item.Type === 'MusicArtist') {
|
|
iconSpan.textContent = 'person';
|
|
} else {
|
|
iconSpan.textContent = 'folder';
|
|
}
|
|
|
|
cardImageContainer.appendChild(iconSpan);
|
|
}
|
|
|
|
// Card overlay container
|
|
const cardOverlayContainer = document.createElement('div');
|
|
cardOverlayContainer.className = 'cardOverlayContainer itemAction';
|
|
cardOverlayContainer.setAttribute('data-action', 'link');
|
|
|
|
// Overlay link
|
|
const overlayLink = document.createElement('a');
|
|
overlayLink.href = `#/details?id=${item.Id}&serverId=${serverId}`;
|
|
overlayLink.className = 'cardImageContainer';
|
|
|
|
// Play button
|
|
const playButton = document.createElement('button');
|
|
playButton.setAttribute('is', 'paper-icon-button-light');
|
|
playButton.className = 'cardOverlayButton cardOverlayButton-hover itemAction paper-icon-button-light cardOverlayFab-primary';
|
|
playButton.setAttribute('data-action', 'resume');
|
|
|
|
const playIcon = document.createElement('span');
|
|
playIcon.className = 'material-icons cardOverlayButtonIcon cardOverlayButtonIcon-hover play_arrow';
|
|
playIcon.setAttribute('aria-hidden', 'true');
|
|
playButton.appendChild(playIcon);
|
|
|
|
// Button container for additional overlay buttons (watchlist, etc.)
|
|
const buttonContainer = document.createElement('div');
|
|
buttonContainer.className = 'cardOverlayButton-br flex';
|
|
|
|
// Add play button to button container
|
|
buttonContainer.appendChild(playButton);
|
|
|
|
// Assemble overlay
|
|
cardOverlayContainer.appendChild(overlayLink);
|
|
cardOverlayContainer.appendChild(buttonContainer);
|
|
|
|
// Card text container - different structure for episodes
|
|
const cardTextContainer = document.createElement('div');
|
|
cardTextContainer.className = 'cardText cardTextCentered cardText-first';
|
|
|
|
if (item.Type === 'Episode') {
|
|
// Episodes: Series name as primary, episode title as secondary
|
|
const seriesLink = document.createElement('a');
|
|
seriesLink.href = `#/details?id=${item.SeriesId || item.Id}&serverId=${serverId}`;
|
|
seriesLink.className = 'itemAction textActionButton';
|
|
seriesLink.setAttribute('data-id', item.SeriesId || item.Id);
|
|
seriesLink.setAttribute('data-serverid', serverId);
|
|
seriesLink.setAttribute('data-type', 'Series');
|
|
seriesLink.setAttribute('data-mediatype', 'undefined');
|
|
seriesLink.setAttribute('data-channelid', 'undefined');
|
|
seriesLink.setAttribute('data-isfolder', 'true');
|
|
seriesLink.setAttribute('data-action', 'link');
|
|
seriesLink.title = item.SeriesName || 'Unknown Series';
|
|
seriesLink.textContent = item.SeriesName || 'Unknown Series';
|
|
|
|
const seriesBdi = document.createElement('bdi');
|
|
seriesBdi.appendChild(seriesLink);
|
|
cardTextContainer.appendChild(seriesBdi);
|
|
|
|
// Episode title as secondary
|
|
const secondaryText = document.createElement('div');
|
|
secondaryText.className = 'cardText cardTextCentered cardText-secondary';
|
|
const episodeLink = document.createElement('a');
|
|
episodeLink.href = `#/details?id=${item.Id}&serverId=${serverId}`;
|
|
episodeLink.className = 'itemAction textActionButton';
|
|
episodeLink.setAttribute('data-id', item.Id);
|
|
episodeLink.setAttribute('data-serverid', serverId);
|
|
episodeLink.setAttribute('data-type', 'Episode');
|
|
episodeLink.setAttribute('data-mediatype', 'undefined');
|
|
episodeLink.setAttribute('data-channelid', 'undefined');
|
|
episodeLink.setAttribute('data-isfolder', 'false');
|
|
episodeLink.setAttribute('data-action', 'link');
|
|
episodeLink.title = item.Name || 'Unknown Episode';
|
|
episodeLink.textContent = item.Name || 'Unknown Episode';
|
|
|
|
const episodeBdi = document.createElement('bdi');
|
|
episodeBdi.appendChild(episodeLink);
|
|
secondaryText.appendChild(episodeBdi);
|
|
} else {
|
|
// Default: Item name as primary, year as secondary
|
|
const titleLink = document.createElement('a');
|
|
titleLink.href = `#/details?id=${item.Id}&serverId=${serverId}`;
|
|
titleLink.className = 'itemAction textActionButton';
|
|
titleLink.setAttribute('data-id', item.Id);
|
|
titleLink.setAttribute('data-serverid', serverId);
|
|
titleLink.setAttribute('data-type', item.Type);
|
|
titleLink.setAttribute('data-mediatype', 'undefined');
|
|
titleLink.setAttribute('data-channelid', 'undefined');
|
|
titleLink.setAttribute('data-isfolder', item.Type === 'MusicAlbum' || item.Type === 'Artist' || item.Type === 'MusicArtist' ? 'true' : 'false');
|
|
titleLink.setAttribute('data-action', 'link');
|
|
titleLink.title = item.Name || 'Unknown';
|
|
titleLink.textContent = item.Name || 'Unknown';
|
|
|
|
const titleBdi = document.createElement('bdi');
|
|
titleBdi.appendChild(titleLink);
|
|
cardTextContainer.appendChild(titleBdi);
|
|
|
|
// Secondary text (year)
|
|
const secondaryText = document.createElement('div');
|
|
secondaryText.className = 'cardText cardTextCentered cardText-secondary';
|
|
const yearBdi = document.createElement('bdi');
|
|
yearBdi.textContent = item.ProductionYear || item.PremiereDate?.substring(0, 4) || '';
|
|
secondaryText.appendChild(yearBdi);
|
|
}
|
|
|
|
// Assemble card
|
|
cardScalable.appendChild(cardPadder);
|
|
cardScalable.appendChild(blurhashCanvas);
|
|
cardScalable.appendChild(cardImageContainer);
|
|
cardScalable.appendChild(cardOverlayContainer);
|
|
|
|
cardBox.appendChild(cardScalable);
|
|
cardBox.appendChild(cardTextContainer);
|
|
|
|
// Add secondary text after primary text
|
|
if (item.Type === 'Episode') {
|
|
const secondaryText = document.createElement('div');
|
|
secondaryText.className = 'cardText cardTextCentered cardText-secondary';
|
|
const episodeLink = document.createElement('a');
|
|
episodeLink.href = `#/details?id=${item.Id}&serverId=${serverId}`;
|
|
episodeLink.className = 'itemAction textActionButton';
|
|
episodeLink.setAttribute('data-id', item.Id);
|
|
episodeLink.setAttribute('data-serverid', serverId);
|
|
episodeLink.setAttribute('data-type', 'Episode');
|
|
episodeLink.setAttribute('data-mediatype', 'undefined');
|
|
episodeLink.setAttribute('data-channelid', 'undefined');
|
|
episodeLink.setAttribute('data-isfolder', 'false');
|
|
episodeLink.setAttribute('data-action', 'link');
|
|
episodeLink.title = item.Name || 'Unknown Episode';
|
|
episodeLink.textContent = item.Name || 'Unknown Episode';
|
|
|
|
const episodeBdi = document.createElement('bdi');
|
|
episodeBdi.appendChild(episodeLink);
|
|
secondaryText.appendChild(episodeBdi);
|
|
cardBox.appendChild(secondaryText);
|
|
} else {
|
|
const secondaryText = document.createElement('div');
|
|
secondaryText.className = 'cardText cardTextCentered cardText-secondary';
|
|
const yearBdi = document.createElement('bdi');
|
|
yearBdi.textContent = item.ProductionYear || item.PremiereDate?.substring(0, 4) || '';
|
|
secondaryText.appendChild(yearBdi);
|
|
cardBox.appendChild(secondaryText);
|
|
}
|
|
|
|
card.appendChild(cardBox);
|
|
|
|
// Add click handler
|
|
card.addEventListener('click', (e) => {
|
|
if (!e.target.closest('button')) {
|
|
console.log('[CardBuilder] item click', { id: item.Id, name: item.Name });
|
|
try {
|
|
Dashboard.navigate(`#!/details?id=${item.Id}&serverId=${serverId}`);
|
|
} catch(e) {
|
|
console.error('[CardBuilder]', e);
|
|
}
|
|
}
|
|
});
|
|
|
|
return card;
|
|
}
|
|
|
|
// Expose the cardBuilder to the global window object
|
|
window.cardBuilder = cardBuilder;
|
|
|
|
console.log('[CardBuilder] Module loaded and available at window.cardBuilder');
|
|
})();
|