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

@@ -74,7 +74,7 @@ function determineCurrentTheme() {
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 === 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
@@ -83,9 +83,9 @@ function determineCurrentTheme() {
if (month === 11) return 'snowflakes'; // snowflakes december
if (month === 0 || month === 1) return 'snowfall'; // 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
//NOT IMPLEMENTED YET
//if (month >= 2 && month <= 4) return 'spring'; // spring march, april, may
@@ -99,12 +99,21 @@ function determineCurrentTheme() {
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
function loadThemeCSS(cssPath) {
if (!cssPath) return;
const link = document.createElement('link');
link.rel = 'stylesheet';
// link.href = resolvePath(cssPath);
link.href = cssPath;
link.onerror = () => {
@@ -121,6 +130,7 @@ function loadThemeJS(jsPath) {
const script = document.createElement('script');
script.src = jsPath;
// script.src = resolvePath(jsPath);
script.defer = true;
script.onerror = () => {
@@ -140,19 +150,29 @@ function updateThemeContainer(containerClass) {
container.className = 'seasonals-container';
document.body.appendChild(container);
}
container.className = `seasonals-container ${containerClass}`;
console.log(`Seasonals-Container class updated to "${containerClass}".`);
}
function removeSelf() {
const script = document.currentScript;
if (script) script.parentNode.removeChild(script);
console.log('External script removed:', script);
}
// function removeSelf() {
// const script = document.currentScript;
// if (script) script.parentNode.removeChild(script);
// console.log('External script removed:', script);
// }
// initialize theme
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 defaultTheme = 'none';
@@ -172,7 +192,11 @@ async function initializeTheme() {
}
let currentTheme;
if (automateThemeSelection === false) {
if (forcedTheme !== 'auto') {
currentTheme = forcedTheme;
console.log(`User forced theme: ${currentTheme}`);
} else if (automateThemeSelection === false) {
currentTheme = defaultTheme;
} else {
currentTheme = determineCurrentTheme();
@@ -182,12 +206,11 @@ async function initializeTheme() {
if (!currentTheme || currentTheme === 'none') {
console.log('No theme selected.');
removeSelf();
return;
}
const theme = themeConfigs[currentTheme];
if (!theme) {
console.error(`Theme "${currentTheme}" not found.`);
return;
@@ -198,9 +221,168 @@ async function initializeTheme() {
if (theme.js) loadThemeJS(theme.js);
console.log(`Theme "${currentTheme}" loaded.`);
removeSelf();
}
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>