From a2a144adf7b9f386339374b7264527c61202038d Mon Sep 17 00:00:00 2001 From: HighImKevin Date: Wed, 26 Nov 2025 05:48:04 -0500 Subject: [PATCH] Fix issues preventing Skin Manager from functioning in JMP. Add GlassFin as a new skin --- scripts/cardBuilder.js | 2 +- scripts/configuration.css | 12 ++---- scripts/homeScreen.css | 4 -- scripts/homeScreen.js | 13 ++++-- scripts/skinManager.js | 91 +++++++++++++++++++++++++++------------ scripts/userConfig.js | 14 ++++++ scripts/utils.js | 38 +++++++++++++++- scripts/watchlist.css | 1 - 8 files changed, 128 insertions(+), 47 deletions(-) diff --git a/scripts/cardBuilder.js b/scripts/cardBuilder.js index 97f7249..0e5fff3 100644 --- a/scripts/cardBuilder.js +++ b/scripts/cardBuilder.js @@ -1016,6 +1016,7 @@ playButton.addEventListener('click', async (e) => { e.stopPropagation(); try { + // TODO - this needs to be updated to use the data-action resume to play the item natively, if possible if (window.apiHelper && window.apiHelper.playItem) { await window.apiHelper.playItem(item.Id); } else { @@ -1449,7 +1450,6 @@ // Add "Show All" button to title if there are more than 20 items if (items.length > 20) { - sectionTitleContainer.style.display = 'flex'; sectionTitleContainer.appendChild(showAllButton); } diff --git a/scripts/configuration.css b/scripts/configuration.css index 3695ae8..8994bea 100644 --- a/scripts/configuration.css +++ b/scripts/configuration.css @@ -45,17 +45,17 @@ order: 1; } -.navMenuOption:has(.build) { +.navMenuOption[data-name="configure"] { order: 9999; margin-top: 3em !important; position: relative; } -.navMenuOption:has(.build)>.material-icons { +.navMenuOption[data-name="configure"]>.material-icons { position: relative; } -.navMenuOption:has(.build)>.material-icons::after { +.navMenuOption[data-name="configure"]>.material-icons::after { content: 'KefinTweaks'; position: absolute; top: -2.5em; @@ -64,10 +64,6 @@ font-size: 14.88px; } -.listItemContent:has([data-skin-name]) { - justify-content: space-between; -} - .listItemContent { width: 100%; } @@ -177,7 +173,7 @@ summary.listItemBodyText { max-width: calc(100% - 4rem); } -.navMenuOption:has(.build):hover:not(.navMenuOption-selected) { +.navMenuOption[data-name="configure"]:hover:not(.navMenuOption-selected) { transform: none !important; margin-top: 3em !important; } \ No newline at end of file diff --git a/scripts/homeScreen.css b/scripts/homeScreen.css index 68c4cbf..e3b2b65 100644 --- a/scripts/homeScreen.css +++ b/scripts/homeScreen.css @@ -75,10 +75,6 @@ margin-top: 0; } -.sectionTitleContainer.sectionTitleContainer-cards.padded-left > .sectionTitle.sectionTitle-cards { - display: flex !important; -} - div#discovery-loading-indicator:not(.show)::before { content: 'Loading...'; position: absolute; diff --git a/scripts/homeScreen.js b/scripts/homeScreen.js index 0e65eb9..e0d9e54 100644 --- a/scripts/homeScreen.js +++ b/scripts/homeScreen.js @@ -2007,13 +2007,18 @@ /** * Navigates to the watchlist tab by clicking the appropriate tab button */ - function getWatchlistUrl() { + async function getWatchlistUrl() { let homePageSuffix = '.html'; if (ApiClient._serverInfo.Version?.split('.')[1] > 10) { homePageSuffix = ''; } - const visibleWatchlistTab = document.querySelector('.libraryPage:not(.hide) .pageTabContent:has(.watchlist.sections)'); - const visibleWatchlistTabIndex = visibleWatchlistTab ? visibleWatchlistTab.getAttribute('data-index') : null; + + if (!window.KefinTweaksUtils || !window.KefinTweaksUtils.getWatchlistTabIndex || !window.KefinTweaksUtils.getWatchlistTabIndex()) { + WARN('KefinTweaksUtils not available'); + return null; + } + + const visibleWatchlistTabIndex = await window.KefinTweaksUtils.getWatchlistTabIndex(); if (visibleWatchlistTabIndex) { return `#/home${homePageSuffix}?tab=${visibleWatchlistTabIndex}`; @@ -2837,7 +2842,7 @@ const scrollableContainer = window.cardBuilder.renderCards( limitedItems, sectionName, - getWatchlistUrl(), + await getWatchlistUrl(), true, cardFormat, sortOrder, diff --git a/scripts/skinManager.js b/scripts/skinManager.js index 1cf0cc7..9fefc51 100644 --- a/scripts/skinManager.js +++ b/scripts/skinManager.js @@ -23,24 +23,50 @@ // Cached server version for synchronous access let cachedServerVersion = null; let versionPollingStarted = false; + + function getMajorServerVersion(version) { + const versionParts = version.split('.'); + if (versionParts.length >= 2) { + const majorVersion = parseInt(versionParts[1], 10); + if (!isNaN(majorVersion)) { + return majorVersion; + } + } + return null; + } /** * Get the current major server version from ApiClient * Polls every 500ms for up to 5 seconds in the background if not immediately available * @returns {number|null} The major version number (e.g., 10 for "10.10.X", 11 for "10.11.X"), or null if unavailable */ - function getCurrentMajorServerVersion() { + async function getCurrentMajorServerVersion() { try { - // Check if ApiClient and serverVersion are available immediately - if (typeof window.ApiClient !== 'undefined' && window.ApiClient._appVersion) { - const versionParts = window.ApiClient._appVersion.split('.'); - if (versionParts.length >= 2) { - const majorVersion = parseInt(versionParts[1], 10); - if (!isNaN(majorVersion)) { - cachedServerVersion = majorVersion; // Cache it - return majorVersion; + if (!window.ApiClient || !window.ApiClient._appName || !window.ApiClient._appVersion) { + return null; + } + + if (window.ApiClient._appName === 'Jellyfin Media Player') { + // Check the server version instead of app version + if (!window.ApiClient._serverVersion) { + // Wait 10s to see if it becomes ready, check every 500ms + const startTime = Date.now(); + while (Date.now() - startTime < 10000) { + if (window.ApiClient._serverVersion) { + break; + } + await new Promise(resolve => setTimeout(resolve, 500)); } } + + cachedServerVersion = getMajorServerVersion(window.ApiClient._serverVersion); + return cachedServerVersion; + } + + // Check if ApiClient and serverVersion are available immediately + if (window.ApiClient._appVersion) { + cachedServerVersion = getMajorServerVersion(window.ApiClient._appVersion); + return cachedServerVersion; } // If we have a cached value, return it @@ -61,15 +87,8 @@ try { if (typeof window.ApiClient !== 'undefined' && window.ApiClient._appVersion) { - const versionParts = window.ApiClient._appVersion.split('.'); - if (versionParts.length >= 2) { - const majorVersion = parseInt(versionParts[1], 10); - if (!isNaN(majorVersion)) { - clearInterval(poll); - cachedServerVersion = majorVersion; - return; - } - } + cachedServerVersion = getMajorServerVersion(window.ApiClient._appVersion); + return; } } catch (error) { WARN('Error getting server version during poll:', error); @@ -97,12 +116,12 @@ * @param {Object} skin - The skin configuration object * @returns {Array|null} Array of URLs to load, or null if no URLs */ - function getSkinUrlsForCurrentVersion(skin) { + async function getSkinUrlsForCurrentVersion(skin) { if (!skin.url) { return null; } - const currentMajorVersion = getCurrentMajorServerVersion(); + const currentMajorVersion = await getCurrentMajorServerVersion(); // New structure: array of objects with majorServerVersions and urls if (Array.isArray(skin.url) && skin.url.length > 0 && typeof skin.url[0] === 'object' && skin.url[0].majorServerVersions) { @@ -151,8 +170,8 @@ * @param {Array} skins - Array of skin configuration objects * @returns {Array} Filtered array of skins compatible with current server version */ - function filterSkinsByServerVersion(skins) { - const currentMajorVersion = getCurrentMajorServerVersion(); + async function filterSkinsByServerVersion(skins) { + const currentMajorVersion = await getCurrentMajorServerVersion(); // If we can't determine the server version, include all skins (backward compatibility) if (currentMajorVersion === null) { @@ -207,10 +226,18 @@ } // Load and merge skin configurations - function loadSkinConfig() { + async function loadSkinConfig() { // Only load from window.KefinTweaksConfig (from JS Injector) const configSkins = window.KefinTweaksConfig?.skins || []; - + + // Add any default skins from userConfig.js that are not already present + const defaultSkins = window.KefinTweaksUserConfig?.skins || []; + defaultSkins.forEach(skin => { + if (!configSkins.some(s => s.name === skin.name)) { + configSkins.push(skin); + } + }); + // Filter to only include enabled skins (enabled !== false) // Default to enabled if the property is not set SKINS_CONFIG = configSkins.filter(skin => { @@ -231,7 +258,7 @@ // Filter skins based on current server version const skinsBeforeFilter = SKINS_CONFIG.length; - SKINS_CONFIG = filterSkinsByServerVersion(SKINS_CONFIG); + SKINS_CONFIG = await filterSkinsByServerVersion(SKINS_CONFIG); const skinsAfterFilter = SKINS_CONFIG.length; if (skinsBeforeFilter !== skinsAfterFilter) { @@ -244,12 +271,20 @@ } // Load and merge theme configurations - function loadThemeConfig() { + async function loadThemeConfig() { const userThemes = window.KefinTweaksUserConfig?.themes || []; const additionalThemes = window.KefinTweaksConfig?.themes || []; // Start with user themes (these take priority) THEMES_CONFIG = [...userThemes]; + + // Add any default themes from userConfig.js that are not already present + const defaultThemes = window.KefinTweaksUserConfig?.themes || []; + defaultThemes.forEach(theme => { + if (!THEMES_CONFIG.some(t => t.name === theme.name)) { + THEMES_CONFIG.push(theme); + } + }); // Add additional themes from main config, avoiding duplicates by name additionalThemes.forEach(theme => { @@ -1637,8 +1672,8 @@ // This prevents the double-reflow/recalc that causes UI freezing // Step 1: Load the new CSS first (gets cached and starts loading) - requestAnimationFrame(() => { - const cssUrls = getSkinUrlsForCurrentVersion(skin); + requestAnimationFrame(async () => { + const cssUrls = await getSkinUrlsForCurrentVersion(skin); if (!cssUrls || cssUrls.length === 0) { LOG(`No CSS URLs found for skin: ${skin.name}`); diff --git a/scripts/userConfig.js b/scripts/userConfig.js index d44b3f3..4ba84d7 100644 --- a/scripts/userConfig.js +++ b/scripts/userConfig.js @@ -61,6 +61,20 @@ ], colorSchemes: [] }, + { + name: 'GlassFin', + author: 'KBH-Reeper', + url: [ + { + majorServerVersions: [10, 11], + urls: [ + 'https://cdn.jsdelivr.net/gh/KBH-Reeper/GlassFin@main/Theme/GlassFin-Theme-V01.03.css', + 'https://cdn.jsdelivr.net/gh/KBH-Reeper/GlassFin@main/Theme/PluginStyling/MediaBar-Plugin-latest.css' + ] + } + ], + colorSchemes: [] + }, { name: 'Jellypane', author: 'tedhinklater', diff --git a/scripts/utils.js b/scripts/utils.js index 18d2557..065ff9c 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -538,6 +538,7 @@ window.KefinTweaksConfig = ${JSON.stringify(configToSave, null, 2)};`; const link = document.createElement('a'); link.setAttribute('is', 'emby-linkbutton'); link.className = 'emby-button navMenuOption lnkMediaFolder'; + link.dataset.name = name.toLowerCase().replace(' ', '-'); link.href = url; if (openInNewTab) { @@ -570,6 +571,40 @@ window.KefinTweaksConfig = ${JSON.stringify(configToSave, null, 2)};`; } } + let _watchlistTabIndex = null; + + /** + * Get watchlist tab index, fetching if not yet set + * @returns {number|null} The watchlist tab index or null if not found + */ + async function getWatchlistTabIndex() { + if (_watchlistTabIndex !== null) { + return _watchlistTabIndex; + } + + // Fetch the tab index as we do in addCustomMenuLink + try { + const response = await fetch(`${ApiClient._serverAddress}/CustomTabs/Config`, { + method: "GET", + headers: { + "Content-Type": "application/json", + "X-Emby-Token": ApiClient._serverInfo.AccessToken || ApiClient.accessToken(), + }, + }); + const data = await response.json(); + data.forEach((tab, index) => { + if (tab.ContentHtml.indexOf('sections watchlist') !== -1) { + _watchlistTabIndex = index + 2; + LOG('Fetched and stored watchlist tab index:', _watchlistTabIndex); + } + }); + } catch (err) { + ERR('Failed to fetch watchlist tab index:', err); + } + + return _watchlistTabIndex; + } + // Expose utilities to global scope window.KefinTweaksUtils = { onViewPage, @@ -578,7 +613,8 @@ window.KefinTweaksConfig = ${JSON.stringify(configToSave, null, 2)};`; getHandlerCount, clearHandlers, addCustomMenuLink, - saveConfigToJavaScriptInjector + saveConfigToJavaScriptInjector, + getWatchlistTabIndex }; LOG('Initialized successfully'); diff --git a/scripts/watchlist.css b/scripts/watchlist.css index cb6fa19..5287e88 100644 --- a/scripts/watchlist.css +++ b/scripts/watchlist.css @@ -2,7 +2,6 @@ font-variation-settings: 'FILL' 1,'wght' 400,'GRAD' 0,'opsz' 20; } .custom-scroller-container h2.sectionTitle.sectionTitle-cards { - display: flex; align-items: center; }