Add client-side settings feature and support for SVG file type

This commit is contained in:
CodeDevMLH
2026-02-04 13:45:11 +01:00
parent c391649884
commit 9259a0f487
5 changed files with 188 additions and 4 deletions

View File

@@ -70,6 +70,7 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Api
if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) return "application/javascript"; if (path.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) return "application/javascript";
if (path.EndsWith(".css", StringComparison.OrdinalIgnoreCase)) return "text/css"; if (path.EndsWith(".css", StringComparison.OrdinalIgnoreCase)) return "text/css";
if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase)) return "text/html"; if (path.EndsWith(".html", StringComparison.OrdinalIgnoreCase)) return "text/html";
if (path.EndsWith(".svg", StringComparison.OrdinalIgnoreCase)) return "image/svg+xml";
return "application/octet-stream"; return "application/octet-stream";
} }
} }

View File

@@ -34,5 +34,6 @@ namespace Jellyfin.Plugin.MediaBarEnhanced.Configuration
public string PreferredVideoQuality { get; set; } = "Auto"; public string PreferredVideoQuality { get; set; } = "Auto";
public bool EnableSeasonalContent { get; set; } = false; public bool EnableSeasonalContent { get; set; } = false;
public bool IsEnabled { get; set; } = true; public bool IsEnabled { get; set; } = true;
public bool EnableClientSideSettings { get; set; } = false;
} }
} }

View File

@@ -221,6 +221,15 @@
Space (pause), M (mute/unmute)) for Space (pause), M (mute/unmute)) for
the slideshow.</div> the slideshow.</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label>
<input is="emby-checkbox" type="checkbox" id="EnableClientSideSettings"
name="EnableClientSideSettings" />
<span>Enable Client-Side Settings</span>
</label>
<div class="fieldDescription">If enabled, users will see a media bar icon in the header to
override settings (like disabling the bar or video backdrops) locally on their device.</div>
</div>
<h2 class="sectionTitle">Time Settings</h2> <h2 class="sectionTitle">Time Settings</h2>
<p>Leave a setting blank to use the default value.</p> <p>Leave a setting blank to use the default value.</p>
@@ -361,7 +370,7 @@
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo', 'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls', 'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen', 'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
'EnableSeasonalContent' 'EnableSeasonalContent', 'EnableClientSideSettings'
]; ];
keys.forEach(function (key) { keys.forEach(function (key) {
@@ -410,7 +419,7 @@
'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo', 'WaitForTrailerToEnd', 'StartMuted', 'FullWidthVideo', 'EnableMobileVideo',
'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls', 'ShowTrailerButton', 'AlwaysShowArrows', 'EnableKeyboardControls',
'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen', 'EnableCustomMediaIds', 'CustomMediaIds', 'EnableLoadingScreen',
'EnableSeasonalContent' 'EnableSeasonalContent', 'EnableClientSideSettings'
]; ];
keys.forEach(function (key) { keys.forEach(function (key) {

View File

@@ -13,6 +13,7 @@
* - option to set a maximum for the pagination dots (will turn into a counter style if exceeded) * - option to set a maximum for the pagination dots (will turn into a counter style if exceeded)
* - option to disable loading screen * - option to disable loading screen
* - option to put collection (boxsets) IDs into the slideshow to display their items * - option to put collection (boxsets) IDs into the slideshow to display their items
* - option to enable client-side settings (allow users to override settings locally on their device)
*/ */
@import url(https://fonts.googleapis.com/css2?family=Archivo+Narrow:ital,wght@0,400..700;1,400..700&display=swap); @import url(https://fonts.googleapis.com/css2?family=Archivo+Narrow:ital,wght@0,400..700;1,400..700&display=swap);

View File

@@ -13,6 +13,7 @@
* - option to set a maximum for the pagination dots (will turn into a counter style if exceeded) * - option to set a maximum for the pagination dots (will turn into a counter style if exceeded)
* - option to disable loading screen * - option to disable loading screen
* - option to put collection (boxsets) IDs into the slideshow to display their items * - option to put collection (boxsets) IDs into the slideshow to display their items
* - option to enable client-side settings (allow users to override settings locally on their device)
*/ */
//Core Module Configuration //Core Module Configuration
@@ -49,6 +50,7 @@ const CONFIG = {
enableSeasonalContent: false, enableSeasonalContent: false,
customMediaIds: "", customMediaIds: "",
enableLoadingScreen: true, enableLoadingScreen: true,
enableClientSideSettings: false,
}; };
// State management // State management
@@ -1440,7 +1442,12 @@ const SlideCreator = {
} }
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
const shouldPlayVideo = CONFIG.enableVideoBackdrop && (!isMobile || CONFIG.enableMobileVideo);
// Client Setting Overrides
const enableVideo = SettingsManager.getSetting('videoBackdrops', CONFIG.enableVideoBackdrop);
const enableMobileVideo = SettingsManager.getSetting('mobileVideo', CONFIG.enableMobileVideo);
const shouldPlayVideo = enableVideo && (!isMobile || enableMobileVideo);
if (trailerUrl && shouldPlayVideo) { if (trailerUrl && shouldPlayVideo) {
let isYoutube = false; let isYoutube = false;
@@ -2774,7 +2781,10 @@ const SlideshowManager = {
this.nextSlide(); this.nextSlide();
}, CONFIG.shuffleInterval); }, CONFIG.shuffleInterval);
if (CONFIG.waitForTrailerToEnd && STATE.slideshow.slideInterval) { // Check if we should wait for trailer
const waitForTrailer = SettingsManager.getSetting('waitForTrailer', CONFIG.waitForTrailerToEnd);
if (waitForTrailer && STATE.slideshow.slideInterval) {
const activeSlide = document.querySelector('.slide.active'); const activeSlide = document.querySelector('.slide.active');
const hasActiveVideo = !!(activeSlide && activeSlide.querySelector('.video-backdrop')); const hasActiveVideo = !!(activeSlide && activeSlide.querySelector('.video-backdrop'));
if (hasActiveVideo) { if (hasActiveVideo) {
@@ -2919,6 +2929,158 @@ const initArrowNavigation = () => {
); );
}; };
const SettingsManager = {
initialized: false,
init() {
if (this.initialized) return;
if (!CONFIG.enableClientSideSettings) return;
this.initialized = true;
this.injectSettingsIcon();
console.log("MediaBarEnhanced: Client-Side Settings Manager initialized.");
},
getSetting(key, defaultValue) {
if (!CONFIG.enableClientSideSettings) return defaultValue;
const value = localStorage.getItem(`mediaBarEnhanced-${key}`);
return value !== null ? value === 'true' : defaultValue;
},
setSetting(key, value) {
localStorage.setItem(`mediaBarEnhanced-${key}`, value);
},
createIcon() {
const button = document.createElement('button');
button.type = 'button';
button.className = 'paper-icon-button-light headerButton media-bar-settings-button';
button.title = 'Media Bar Settings';
// button.innerHTML = '<span class="material-icons">tune</span>';
button.innerHTML = '<img src="/MediaBarEnhanced/Resources/assets/logo_SW.svg" style="width: 24px; height: 24px; vertical-align: middle;">';
button.style.verticalAlign = 'middle';
button.addEventListener('click', (e) => {
e.stopPropagation();
this.toggleSettingsPopup(button);
});
return button;
},
injectSettingsIcon() {
const observer = new MutationObserver((mutations, obs) => {
const headerRight = document.querySelector('.headerRight');
if (headerRight && !document.querySelector('.media-bar-settings-button')) {
const icon = this.createIcon();
headerRight.prepend(icon);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
},
createPopup(anchorElement) {
const existing = document.querySelector('.media-bar-settings-popup');
if (existing) existing.remove();
const popup = document.createElement('div');
popup.className = 'media-bar-settings-popup dialog';
Object.assign(popup.style, {
position: 'fixed',
zIndex: '10000',
backgroundColor: '#202020',
padding: '1em',
borderRadius: '0.3em',
boxShadow: '0 0 20px rgba(0,0,0,0.5)',
minWidth: '250px',
color: '#fff',
});
const rect = anchorElement.getBoundingClientRect();
let rightPos = window.innerWidth - rect.right;
if (window.innerWidth < 450 || (window.innerWidth - rightPos) < 260) {
popup.style.right = '1rem';
popup.style.left = 'auto';
} else {
popup.style.right = `${rightPos}px`;
popup.style.left = 'auto';
}
popup.style.top = `${rect.bottom + 10}px`;
const settings = [
{ key: 'enabled', label: 'Enable Media Bar', default: true },
{ key: 'videoBackdrops', label: 'Enable Video Backdrops', default: CONFIG.enableVideoBackdrop },
{ key: 'trailerButton', label: 'Show Trailer Button', default: CONFIG.showTrailerButton },
{ key: 'mobileVideo', label: 'Enable Mobile Video', default: CONFIG.enableMobileVideo },
{ key: 'waitForTrailer', label: 'Wait For Trailer To End', default: CONFIG.waitForTrailerToEnd },
{ key: 'slideAnimations', label: 'Enable Slide Animations', default: CONFIG.slideAnimationEnabled },
];
let html = '<h3 style="margin-top:0; margin-bottom:1em; border-bottom:1px solid #444; padding-bottom:0.5em;">Media Bar Settings</h3>';
settings.forEach(setting => {
const isChecked = this.getSetting(setting.key, setting.default);
html += `
<div class="checkboxContainer checkboxContainer-withDescription" style="margin-bottom: 0.5em;">
<label class="emby-checkbox-label">
<input id="mb-setting-${setting.key}" type="checkbox" is="emby-checkbox" class="emby-checkbox" ${isChecked ? 'checked' : ''} />
<span class="checkboxLabel">${setting.label}</span>
</label>
</div>
`;
});
// Reload button
html += `
<div style="margin-top:1em; text-align:right;">
<button is="emby-button" type="button" class="raised button-submit emby-button" id="mb-settings-save">
<span>Reload</span>
</button>
</div>
`;
popup.innerHTML = html;
// Add Listeners
settings.forEach(setting => {
const checkbox = popup.querySelector(`#mb-setting-${setting.key}`);
checkbox.addEventListener('change', (e) => {
this.setSetting(setting.key, e.target.checked);
});
});
popup.querySelector('#mb-settings-save').addEventListener('click', () => {
location.reload();
});
const closeHandler = (e) => {
if (!popup.contains(e.target) && e.target !== anchorElement && !anchorElement.contains(e.target)) {
popup.remove();
document.removeEventListener('click', closeHandler);
}
};
setTimeout(() => document.addEventListener('click', closeHandler), 0);
document.body.appendChild(popup);
},
toggleSettingsPopup(anchorElement) {
const existing = document.querySelector('.media-bar-settings-popup');
if (existing) {
existing.remove();
} else {
this.createPopup(anchorElement);
}
}
};
/** /**
* Initialize the slideshow * Initialize the slideshow
*/ */
@@ -2927,6 +3089,16 @@ const slidesInit = async () => {
console.log("⚠️ Slideshow already initialized, skipping"); console.log("⚠️ Slideshow already initialized, skipping");
return; return;
} }
if (CONFIG.enableClientSideSettings) {
SettingsManager.init();
const isEnabled = SettingsManager.getSetting('enabled', true);
if (!isEnabled) {
console.log("MediaBarEnhanced: Disabled by client-side setting.");
return;
}
}
STATE.slideshow.hasInitialized = true; STATE.slideshow.hasInitialized = true;
/** /**