Add client-side toggle option for seasonal settings
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s

This commit is contained in:
CodeDevMLH
2026-02-03 18:48:11 +01:00
parent 0177a7caea
commit f036e748da
6 changed files with 394 additions and 104 deletions

View File

@@ -15,6 +15,7 @@ public class PluginConfiguration : BasePluginConfiguration
IsEnabled = true; IsEnabled = true;
SelectedSeason = "none"; SelectedSeason = "none";
AutomateSeasonSelection = true; AutomateSeasonSelection = true;
EnableClientSideToggle = true;
Autumn = new AutumnOptions(); Autumn = new AutumnOptions();
Snowflakes = new SnowflakesOptions(); Snowflakes = new SnowflakesOptions();
@@ -43,6 +44,14 @@ public class PluginConfiguration : BasePluginConfiguration
/// </summary> /// </summary>
public bool AutomateSeasonSelection { get; set; } public bool AutomateSeasonSelection { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to enable client-side toggle for users.
/// </summary>
public bool EnableClientSideToggle { get; set; }
/// <summary>
/// Gets or sets the Seasonals options.
/// </summary>
public AutumnOptions Autumn { get; set; } public AutumnOptions Autumn { get; set; }
public SnowflakesOptions Snowflakes { get; set; } public SnowflakesOptions Snowflakes { get; set; }
public SnowfallOptions Snowfall { get; set; } public SnowfallOptions Snowfall { get; set; }

View File

@@ -6,17 +6,17 @@
</head> </head>
<body> <body>
<div id="SeasonalsConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox"> <div id="SeasonalsConfigPage" data-role="page" class="page type-interior pluginConfigurationPage" data-require="emby-input,emby-button,emby-select,emby-checkbox">
<style> <style>
select option:disabled { select option:disabled {
color: #a3a3a3 !important; color: #a3a3a3 !important;
} }
</style> </style>
<div data-role="content"> <div data-role="content">
<div class="content-primary"> <div class="content-primary">
<div class="sectionTitleContainer"> <div class="sectionTitleContainer">
<h2 class="sectionTitle">Seasonals</h2> <h2 class="sectionTitle">Seasonals</h2>
<a is="emby-linkbutton" class="raised raised-mini emby-button" style="margin-left: 2em;" <a is="emby-linkbutton" class="raised raised-mini emby-button" style="margin-left: 2em;"
target="_blank" href="https://github.com/CodeDevMLH/Jellyfin-Seasonals"> target="_blank" href="https://github.com/CodeDevMLH/Jellyfin-Seasonals">
<i class="md-icon button-icon button-icon-left secondaryText"></i> <i class="md-icon button-icon button-icon-left secondaryText"></i>
<span>Help</span> <span>Help</span>
</a> </a>
@@ -38,6 +38,15 @@
</label> </label>
<div class="fieldDescription">Automatically select the season based on the date.</div> <div class="fieldDescription">Automatically select the season based on the date.</div>
</div> </div>
<div class="checkboxContainer checkboxContainer-withDescription">
<label class="emby-checkbox-label">
<input id="EnableClientSideToggle" name="EnableClientSideToggle" type="checkbox"
is="emby-checkbox" />
<span>Allow Client-Side Toggle</span>
</label>
<div class="fieldDescription">If enabled, users will see a settings icon in the header to toggle
animations for their browser.</div>
</div>
<div class="selectContainer"> <div class="selectContainer">
<label class="selectLabel" for="SelectedSeason">Selected Season</label> <label class="selectLabel" for="SelectedSeason">Selected Season</label>
<select is="emby-select" id="SelectedSeason" name="SelectedSeason" class="emby-select-withcolor emby-select"> <select is="emby-select" id="SelectedSeason" name="SelectedSeason" class="emby-select-withcolor emby-select">
@@ -530,7 +539,8 @@
document.querySelector('#IsEnabled').checked = config.IsEnabled; document.querySelector('#IsEnabled').checked = config.IsEnabled;
document.querySelector('#SelectedSeason').value = config.SelectedSeason; document.querySelector('#SelectedSeason').value = config.SelectedSeason;
document.querySelector('#AutomateSeasonSelection').checked = config.AutomateSeasonSelection; document.querySelector('#AutomateSeasonSelection').checked = config.AutomateSeasonSelection;
document.querySelector('#EnableClientSideToggle').checked = config.EnableClientSideToggle !== undefined ? config.EnableClientSideToggle : true;
// Advanced Config // Advanced Config
// Autumn // Autumn
document.querySelector('#EnableAutumn').checked = config.Autumn.EnableAutumn; document.querySelector('#EnableAutumn').checked = config.Autumn.EnableAutumn;
@@ -621,98 +631,100 @@
document.querySelector('#SeasonalsConfigForm') document.querySelector('#SeasonalsConfigForm')
.addEventListener('submit', function(e) { .addEventListener('submit', function(e) {
Dashboard.showLoadingMsg(); Dashboard.showLoadingMsg();
ApiClient.getPluginConfiguration(SeasonalsConfig.pluginUniqueId).then(function (config) { ApiClient.getPluginConfiguration(SeasonalsConfig.pluginUniqueId).then(function (config) {
config.IsEnabled = document.querySelector('#IsEnabled').checked; config.IsEnabled = document.querySelector('#IsEnabled').checked;
config.SelectedSeason = document.querySelector('#SelectedSeason').value; config.SelectedSeason = document.querySelector('#SelectedSeason').value;
config.AutomateSeasonSelection = document.querySelector('#AutomateSeasonSelection').checked; config.AutomateSeasonSelection = document.querySelector('#AutomateSeasonSelection').checked;
config.EnableClientSideToggle = document.querySelector('#EnableClientSideToggle').checked;
// Advanced Config
// Autumn
config.Autumn.EnableAutumn = document.querySelector('#EnableAutumn').checked;
config.Autumn.LeafCount = parseInt(document.querySelector('#AutumnLeafCount').value);
config.Autumn.EnableRandomLeaves = document.querySelector('#EnableRandomLeaves').checked;
config.Autumn.EnableRandomLeavesMobile = document.querySelector('#EnableRandomLeavesMobile').checked;
config.Autumn.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationAutumn').checked;
config.Autumn.EnableRotation = document.querySelector('#EnableRotation').checked;
// Snowflakes
config.Snowflakes.SnowflakeCount = parseInt(document.querySelector('#SnowflakesCount').value);
config.Snowflakes.EnableSnowflakes = document.querySelector('#EnableSnowflakes').checked;
config.Snowflakes.EnableRandomSnowflakes = document.querySelector('#EnableRandomSnowflakes').checked;
config.Snowflakes.EnableRandomSnowflakesMobile = document.querySelector('#EnableRandomSnowflakesMobile').checked;
config.Snowflakes.EnableColoredSnowflakes = document.querySelector('#EnableColoredSnowflakes').checked;
config.Snowflakes.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSnowflakes').checked;
// Snowfall // Advanced Config
config.Snowfall.EnableSnowfall = document.querySelector('#EnableSnowfall').checked; // Autumn
config.Snowfall.SnowflakesCount = parseInt(document.querySelector('#SnowfallCount').value); config.Autumn.EnableAutumn = document.querySelector('#EnableAutumn').checked;
config.Snowfall.SnowflakesCountMobile = parseInt(document.querySelector('#SnowfallCountMobile').value); config.Autumn.LeafCount = parseInt(document.querySelector('#AutumnLeafCount').value);
config.Snowfall.Speed = parseFloat(document.querySelector('#SnowfallSpeed').value); config.Autumn.EnableRandomLeaves = document.querySelector('#EnableRandomLeaves').checked;
config.Autumn.EnableRandomLeavesMobile = document.querySelector('#EnableRandomLeavesMobile').checked;
config.Autumn.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationAutumn').checked;
config.Autumn.EnableRotation = document.querySelector('#EnableRotation').checked;
// Snowstorm // Snowflakes
config.Snowstorm.EnableSnowstorm = document.querySelector('#EnableSnowstorm').checked; config.Snowflakes.SnowflakeCount = parseInt(document.querySelector('#SnowflakesCount').value);
config.Snowstorm.SnowflakesCount = parseInt(document.querySelector('#SnowstormCount').value); config.Snowflakes.EnableSnowflakes = document.querySelector('#EnableSnowflakes').checked;
config.Snowstorm.SnowflakesCountMobile = parseInt(document.querySelector('#SnowstormCountMobile').value); config.Snowflakes.EnableRandomSnowflakes = document.querySelector('#EnableRandomSnowflakes').checked;
config.Snowstorm.Speed = parseFloat(document.querySelector('#SnowstormSpeed').value); config.Snowflakes.EnableRandomSnowflakesMobile = document.querySelector('#EnableRandomSnowflakesMobile').checked;
config.Snowstorm.HorizontalWind = parseFloat(document.querySelector('#SnowstormHorizontalWind').value); config.Snowflakes.EnableColoredSnowflakes = document.querySelector('#EnableColoredSnowflakes').checked;
config.Snowstorm.VerticalVariation = parseFloat(document.querySelector('#SnowstormVerticalVariation').value); config.Snowflakes.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationSnowflakes').checked;
// Fireworks // Snowfall
config.Fireworks.EnableFireworks = document.querySelector('#EnableFireworks').checked; config.Snowfall.EnableSnowfall = document.querySelector('#EnableSnowfall').checked;
config.Fireworks.ParticleCount = parseInt(document.querySelector('#FireworksParticles').value); config.Snowfall.SnowflakesCount = parseInt(document.querySelector('#SnowfallCount').value);
config.Fireworks.LaunchInterval = parseInt(document.querySelector('#FireworksInterval').value); config.Snowfall.SnowflakesCountMobile = parseInt(document.querySelector('#SnowfallCountMobile').value);
config.Fireworks.ScrollFireworks = document.querySelector('#ScrollFireworks').checked; config.Snowfall.Speed = parseFloat(document.querySelector('#SnowfallSpeed').value);
config.Fireworks.MinFireworks = parseInt(document.querySelector('#MinFireworks').value);
config.Fireworks.MaxFireworks = parseInt(document.querySelector('#MaxFireworks').value);
// Halloween // Snowstorm
config.Halloween.EnableHalloween = document.querySelector('#EnableHalloween').checked; config.Snowstorm.EnableSnowstorm = document.querySelector('#EnableSnowstorm').checked;
config.Halloween.SymbolCount = parseInt(document.querySelector('#HalloweenCount').value); config.Snowstorm.SnowflakesCount = parseInt(document.querySelector('#SnowstormCount').value);
config.Halloween.EnableRandomSymbols = document.querySelector('#EnableRandomHalloween').checked; config.Snowstorm.SnowflakesCountMobile = parseInt(document.querySelector('#SnowstormCountMobile').value);
config.Halloween.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomHalloweenMobile').checked; config.Snowstorm.Speed = parseFloat(document.querySelector('#SnowstormSpeed').value);
config.Halloween.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationHalloween').checked; config.Snowstorm.HorizontalWind = parseFloat(document.querySelector('#SnowstormHorizontalWind').value);
config.Snowstorm.VerticalVariation = parseFloat(document.querySelector('#SnowstormVerticalVariation').value);
// Hearts // Fireworks
config.Hearts.EnableHearts = document.querySelector('#EnableHearts').checked; config.Fireworks.EnableFireworks = document.querySelector('#EnableFireworks').checked;
config.Hearts.SymbolCount = parseInt(document.querySelector('#HeartsCount').value); config.Fireworks.ParticleCount = parseInt(document.querySelector('#FireworksParticles').value);
config.Hearts.EnableRandomSymbols = document.querySelector('#EnableRandomHearts').checked; config.Fireworks.LaunchInterval = parseInt(document.querySelector('#FireworksInterval').value);
config.Hearts.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomHeartsMobile').checked; config.Fireworks.ScrollFireworks = document.querySelector('#ScrollFireworks').checked;
config.Hearts.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationHearts').checked; config.Fireworks.MinFireworks = parseInt(document.querySelector('#MinFireworks').value);
config.Fireworks.MaxFireworks = parseInt(document.querySelector('#MaxFireworks').value);
// Christmas // Halloween
config.Christmas.EnableChristmas = document.querySelector('#EnableChristmas').checked; config.Halloween.EnableHalloween = document.querySelector('#EnableHalloween').checked;
config.Christmas.SymbolCount = parseInt(document.querySelector('#ChristmasCount').value); config.Halloween.SymbolCount = parseInt(document.querySelector('#HalloweenCount').value);
config.Christmas.EnableRandomChristmas = document.querySelector('#EnableRandomChristmas').checked; config.Halloween.EnableRandomSymbols = document.querySelector('#EnableRandomHalloween').checked;
config.Christmas.EnableRandomChristmasMobile = document.querySelector('#EnableRandomChristmasMobile').checked; config.Halloween.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomHalloweenMobile').checked;
config.Christmas.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationChristmas').checked; config.Halloween.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationHalloween').checked;
// Santa // Hearts
config.Santa.EnableSanta = document.querySelector('#EnableSanta').checked; config.Hearts.EnableHearts = document.querySelector('#EnableHearts').checked;
config.Santa.SnowflakesCount = parseInt(document.querySelector('#SantaSnowflakes').value); config.Hearts.SymbolCount = parseInt(document.querySelector('#HeartsCount').value);
config.Santa.SnowflakesCountMobile = parseInt(document.querySelector('#SantaSnowflakesMobile').value); config.Hearts.EnableRandomSymbols = document.querySelector('#EnableRandomHearts').checked;
config.Santa.SantaSpeed = parseFloat(document.querySelector('#SantaSpeed').value); config.Hearts.EnableRandomSymbolsMobile = document.querySelector('#EnableRandomHeartsMobile').checked;
config.Santa.SantaSpeedMobile = parseFloat(document.querySelector('#SantaSpeedMobile').value); config.Hearts.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationHearts').checked;
config.Santa.SnowFallSpeed = parseFloat(document.querySelector('#SantaSnowFallSpeed').value);
config.Santa.MaxSantaRestTime = parseFloat(document.querySelector('#MaxSantaRestTime').value);
config.Santa.MinSantaRestTime = parseFloat(document.querySelector('#MinSantaRestTime').value);
config.Santa.MaxPresentFallSpeed = parseFloat(document.querySelector('#MaxPresentFallSpeed').value);
config.Santa.MinPresentFallSpeed = parseFloat(document.querySelector('#MinPresentFallSpeed').value);
// Easter // Christmas
config.Easter.EnableEaster = document.querySelector('#EnableEaster').checked; config.Christmas.EnableChristmas = document.querySelector('#EnableChristmas').checked;
config.Easter.EggCount = parseInt(document.querySelector('#EasterEggCount').value); config.Christmas.SymbolCount = parseInt(document.querySelector('#ChristmasCount').value);
config.Easter.EnableRandomEaster = document.querySelector('#EnableRandomEaster').checked; config.Christmas.EnableRandomChristmas = document.querySelector('#EnableRandomChristmas').checked;
config.Easter.EnableRandomEasterMobile = document.querySelector('#EnableRandomEasterMobile').checked; config.Christmas.EnableRandomChristmasMobile = document.querySelector('#EnableRandomChristmasMobile').checked;
config.Easter.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationEaster').checked; config.Christmas.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationChristmas').checked;
config.Easter.EnableBunny = document.querySelector('#EasterBunny').checked;
config.Easter.BunnyDuration = parseInt(document.querySelector('#BunnyDuration').value);
config.Easter.HopHeight = parseInt(document.querySelector('#HopHeight').value);
config.Easter.MinBunnyRestTime = parseInt(document.querySelector('#MinBunnyRestTime').value);
config.Easter.MaxBunnyRestTime = parseInt(document.querySelector('#MaxBunnyRestTime').value);
ApiClient.updatePluginConfiguration(SeasonalsConfig.pluginUniqueId, config).then(function (result) { // Santa
Dashboard.processPluginConfigurationUpdateResult(result); config.Santa.EnableSanta = document.querySelector('#EnableSanta').checked;
config.Santa.SnowflakesCount = parseInt(document.querySelector('#SantaSnowflakes').value);
config.Santa.SnowflakesCountMobile = parseInt(document.querySelector('#SantaSnowflakesMobile').value);
config.Santa.SantaSpeed = parseFloat(document.querySelector('#SantaSpeed').value);
config.Santa.SantaSpeedMobile = parseFloat(document.querySelector('#SantaSpeedMobile').value);
config.Santa.SnowFallSpeed = parseFloat(document.querySelector('#SantaSnowFallSpeed').value);
config.Santa.MaxSantaRestTime = parseFloat(document.querySelector('#MaxSantaRestTime').value);
config.Santa.MinSantaRestTime = parseFloat(document.querySelector('#MinSantaRestTime').value);
config.Santa.MaxPresentFallSpeed = parseFloat(document.querySelector('#MaxPresentFallSpeed').value);
config.Santa.MinPresentFallSpeed = parseFloat(document.querySelector('#MinPresentFallSpeed').value);
// Easter
config.Easter.EnableEaster = document.querySelector('#EnableEaster').checked;
config.Easter.EggCount = parseInt(document.querySelector('#EasterEggCount').value);
config.Easter.EnableRandomEaster = document.querySelector('#EnableRandomEaster').checked;
config.Easter.EnableRandomEasterMobile = document.querySelector('#EnableRandomEasterMobile').checked;
config.Easter.EnableDifferentDuration = document.querySelector('#EnableDifferentDurationEaster').checked;
config.Easter.EnableBunny = document.querySelector('#EasterBunny').checked;
config.Easter.BunnyDuration = parseInt(document.querySelector('#BunnyDuration').value);
config.Easter.HopHeight = parseInt(document.querySelector('#HopHeight').value);
config.Easter.MinBunnyRestTime = parseInt(document.querySelector('#MinBunnyRestTime').value);
config.Easter.MaxBunnyRestTime = parseInt(document.querySelector('#MaxBunnyRestTime').value);
ApiClient.updatePluginConfiguration(SeasonalsConfig.pluginUniqueId, config).then(function (result) {
Dashboard.processPluginConfigurationUpdateResult(result);
}); });
}); });

View File

@@ -12,7 +12,7 @@
<!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> --> <!-- <TreatWarningsAsErrors>false</TreatWarningsAsErrors> -->
<Title>Jellyfin Seasonals Plugin</Title> <Title>Jellyfin Seasonals Plugin</Title>
<Authors>CodeDevMLH</Authors> <Authors>CodeDevMLH</Authors>
<Version>1.5.1.0</Version> <Version>1.6.0.0</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl> <RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl>
</PropertyGroup> </PropertyGroup>

View File

@@ -74,7 +74,7 @@ function determineCurrentTheme() {
const day = date.getDate(); // 1-31 const day = date.getDate(); // 1-31
if ((month === 11 && day >= 28) || (month === 0 && day <= 5)) return 'fireworks'; //new year fireworks december 28 - january 5 if ((month === 11 && day >= 28) || (month === 0 && day <= 5)) return 'fireworks'; //new year fireworks december 28 - january 5
if (month === 1 && day >= 10 && day <= 18) return 'hearts'; // valentine's day february 10 - 18 if (month === 1 && day >= 10 && day <= 18) return 'hearts'; // valentine's day february 10 - 18
if (month === 11 && day >= 22 && day <= 27) return 'santa'; // christmas december 22 - 27 if (month === 11 && day >= 22 && day <= 27) return 'santa'; // christmas december 22 - 27
@@ -83,9 +83,9 @@ function determineCurrentTheme() {
if (month === 11) return 'snowflakes'; // snowflakes december if (month === 11) return 'snowflakes'; // snowflakes december
if (month === 0 || month === 1) return 'snowfall'; // snow january, february if (month === 0 || month === 1) return 'snowfall'; // snow january, february
// if (month === 0 || month === 1) return 'snowstorm'; // snow january, february // if (month === 0 || month === 1) return 'snowstorm'; // snow january, february
if ((month === 2 && day >= 25) || (month === 3 && day <= 25)) return 'easter'; // easter march 25 - april 25 if ((month === 2 && day >= 25) || (month === 3 && day <= 25)) return 'easter'; // easter march 25 - april 25
//NOT IMPLEMENTED YET //NOT IMPLEMENTED YET
//if (month >= 2 && month <= 4) return 'spring'; // spring march, april, may //if (month >= 2 && month <= 4) return 'spring'; // spring march, april, may
@@ -99,12 +99,21 @@ function determineCurrentTheme() {
return 'none'; // Fallback (nothing) return 'none'; // Fallback (nothing)
} }
// helper to resolve paths for local testing vs production
function resolvePath(path) {
if (window.location.protocol === 'file:' || window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
return path.replace('/Seasonals/Resources/', './');
}
return path;
}
// load theme csss // load theme csss
function loadThemeCSS(cssPath) { function loadThemeCSS(cssPath) {
if (!cssPath) return; if (!cssPath) return;
const link = document.createElement('link'); const link = document.createElement('link');
link.rel = 'stylesheet'; link.rel = 'stylesheet';
// link.href = resolvePath(cssPath);
link.href = cssPath; link.href = cssPath;
link.onerror = () => { link.onerror = () => {
@@ -121,6 +130,7 @@ function loadThemeJS(jsPath) {
const script = document.createElement('script'); const script = document.createElement('script');
script.src = jsPath; script.src = jsPath;
// script.src = resolvePath(jsPath);
script.defer = true; script.defer = true;
script.onerror = () => { script.onerror = () => {
@@ -140,19 +150,29 @@ function updateThemeContainer(containerClass) {
container.className = 'seasonals-container'; container.className = 'seasonals-container';
document.body.appendChild(container); document.body.appendChild(container);
} }
container.className = `seasonals-container ${containerClass}`; container.className = `seasonals-container ${containerClass}`;
console.log(`Seasonals-Container class updated to "${containerClass}".`); console.log(`Seasonals-Container class updated to "${containerClass}".`);
} }
function removeSelf() { // function removeSelf() {
const script = document.currentScript; // const script = document.currentScript;
if (script) script.parentNode.removeChild(script); // if (script) script.parentNode.removeChild(script);
console.log('External script removed:', script); // console.log('External script removed:', script);
} // }
// initialize theme // initialize theme
async function initializeTheme() { async function initializeTheme() {
// Check local user preference
const isEnabled = getSavedSetting('seasonals-enabled', 'true') === 'true';
if (!isEnabled) {
console.log('Seasonals disabled by user preference.');
return;
}
const forcedTheme = getSavedSetting('seasonals-theme', 'auto');
let automateThemeSelection = true; let automateThemeSelection = true;
let defaultTheme = 'none'; let defaultTheme = 'none';
@@ -172,7 +192,11 @@ async function initializeTheme() {
} }
let currentTheme; let currentTheme;
if (automateThemeSelection === false) {
if (forcedTheme !== 'auto') {
currentTheme = forcedTheme;
console.log(`User forced theme: ${currentTheme}`);
} else if (automateThemeSelection === false) {
currentTheme = defaultTheme; currentTheme = defaultTheme;
} else { } else {
currentTheme = determineCurrentTheme(); currentTheme = determineCurrentTheme();
@@ -182,12 +206,11 @@ async function initializeTheme() {
if (!currentTheme || currentTheme === 'none') { if (!currentTheme || currentTheme === 'none') {
console.log('No theme selected.'); console.log('No theme selected.');
removeSelf();
return; return;
} }
const theme = themeConfigs[currentTheme]; const theme = themeConfigs[currentTheme];
if (!theme) { if (!theme) {
console.error(`Theme "${currentTheme}" not found.`); console.error(`Theme "${currentTheme}" not found.`);
return; return;
@@ -198,9 +221,168 @@ async function initializeTheme() {
if (theme.js) loadThemeJS(theme.js); if (theme.js) loadThemeJS(theme.js);
console.log(`Theme "${currentTheme}" loaded.`); console.log(`Theme "${currentTheme}" loaded.`);
removeSelf();
} }
initializeTheme(); initializeTheme();
// User UI Seasonal Settings
function getSavedSetting(key, defaultValue) {
const value = localStorage.getItem(key);
return value !== null ? value : defaultValue;
}
function setSavedSetting(key, value) {
localStorage.setItem(key, value);
}
function createSettingsIcon() {
const button = document.createElement('button');
button.type = 'button';
button.className = 'paper-icon-button-light headerButton seasonal-settings-button';
button.title = 'Seasonal Settings';
button.innerHTML = '<span class="material-icons">ac_unit</span>';
button.style.verticalAlign = 'middle';
button.addEventListener('click', (e) => {
e.stopPropagation();
toggleSettingsPopup(button);
});
return button;
}
function createSettingsPopup(anchorElement) {
const existing = document.querySelector('.seasonal-settings-popup');
if (existing) existing.remove();
const popup = document.createElement('div');
popup.className = 'seasonal-settings-popup dialog';
Object.assign(popup.style, {
position: 'fixed',
zIndex: '10000',
backgroundColor: '#202020',
padding: '1em',
borderRadius: '4px',
boxShadow: '0 0 20px rgba(0,0,0,0.5)',
minWidth: '200px',
color: '#fff'
});
const rect = anchorElement.getBoundingClientRect();
popup.style.top = `${rect.bottom + 10}px`;
popup.style.right = `${window.innerWidth - rect.right}px`;
const enabledContainer = document.createElement('div');
enabledContainer.style.marginBottom = '1em';
enabledContainer.style.display = 'flex';
enabledContainer.style.alignItems = 'center';
enabledContainer.style.justifyContent = 'space-between';
const enabledLabel = document.createElement('label');
enabledLabel.textContent = 'Enable Seasonals';
enabledLabel.style.marginRight = '10px';
const enabledCheckbox = document.createElement('input');
enabledCheckbox.type = 'checkbox';
enabledCheckbox.checked = getSavedSetting('seasonals-enabled', 'true') === 'true';
enabledCheckbox.addEventListener('change', (e) => {
setSavedSetting('seasonals-enabled', e.target.checked);
location.reload();
});
enabledContainer.appendChild(enabledLabel);
enabledContainer.appendChild(enabledCheckbox);
popup.appendChild(enabledContainer);
// Theme Selector
const themeContainer = document.createElement('div');
themeContainer.style.display = 'flex';
themeContainer.style.flexDirection = 'column';
const themeLabel = document.createElement('label');
themeLabel.textContent = 'Force Theme';
themeLabel.style.marginBottom = '0.5em';
const themeSelect = document.createElement('select');
themeSelect.style.width = '100%';
themeSelect.style.padding = '0.5em';
themeSelect.style.backgroundColor = '#333';
themeSelect.style.color = '#fff';
themeSelect.style.border = '1px solid #444';
themeSelect.style.borderRadius = '4px';
// Add Auto option
const autoOption = document.createElement('option');
autoOption.value = 'auto';
autoOption.textContent = 'Auto (Date Based)';
themeSelect.appendChild(autoOption);
// Add available themes
Object.keys(themeConfigs).forEach(key => {
if (key === 'none') return;
const option = document.createElement('option');
option.value = key;
// Capitalize first letter
option.textContent = key.charAt(0).toUpperCase() + key.slice(1);
themeSelect.appendChild(option);
});
// Set current value
themeSelect.value = getSavedSetting('seasonals-theme', 'auto');
themeSelect.addEventListener('change', (e) => {
setSavedSetting('seasonals-theme', e.target.value);
location.reload();
});
themeContainer.appendChild(themeLabel);
themeContainer.appendChild(themeSelect);
popup.appendChild(themeContainer);
// Close on click outside
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);
}
function toggleSettingsPopup(anchorElement) {
const existing = document.querySelector('.seasonal-settings-popup');
if (existing) {
existing.remove();
} else {
createSettingsPopup(anchorElement);
}
}
function injectSettingsIcon() {
const observer = new MutationObserver((mutations, obs) => {
// Check if admin has enabled this feature
if (window.SeasonalsPluginConfig && window.SeasonalsPluginConfig.EnableClientSideToggle === false) {
return;
}
const headerRight = document.querySelector('.headerRight');
if (headerRight && !document.querySelector('.seasonal-settings-button')) {
const icon = createSettingsIcon();
headerRight.prepend(icon);
// obs.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
injectSettingsIcon();

View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Seasonals Test Display</title>
<link rel="stylesheet" href="snowfall.css">
<style>
body {
background-color: black;
color: white;
margin: 0;
font-family: sans-serif;
}
/* Mock Jellyfin Header */
.skinHeader {
background-color: #101010;
height: 60px;
display: flex;
align-items: center;
padding: 0 1em;
border-bottom: 1px solid #333;
}
.headerRight {
margin-left: auto;
display: flex;
align-items: center;
}
.paper-icon-button-light {
background: none;
border: none;
color: white;
cursor: pointer;
padding: 10px;
}
.material-icons {
font-family: 'Material Icons';
/* Placeholder if not loaded */
font-weight: normal;
font-style: normal;
font-size: 24px;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
}
</style>
<!-- Load Material Icons for the snowflake -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<!-- Mock Header Structure -->
<div class="skinHeader">
<div class="skinHeader-content">
<span>Jellyfin Mock Header</span>
</div>
<div class="headerRight">
<button class="paper-icon-button-light">
<span class="material-icons">person</span>
</button>
</div>
</div>
<div class="seasonals-container"></div>
<!-- Load the main script -->
<script src="seasonals.js"></script>
</body>
</html>

View File

@@ -8,6 +8,14 @@
"category": "General", "category": "General",
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png", "imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/raw/branch/main/logo.png",
"versions": [ "versions": [
{
"version": "1.6.0.0",
"changelog": "- feat: Add client-side toggle option for seasonal settings",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.6.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "",
"timestamp": ""
},
{ {
"version": "1.5.1.0", "version": "1.5.1.0",
"changelog": "- fix: snowfall effect sometimes not scaling on window resize, which leads to clustering and rain effect of snowflakes", "changelog": "- fix: snowfall effect sometimes not scaling on window resize, which leads to clustering and rain effect of snowflakes",