Compare commits

..

78 Commits

Author SHA1 Message Date
CodeDevMLH
2572e085f6 Update manifest.json for release v1.6.4.0 [skip ci] 2026-02-03 18:46:36 +00:00
CodeDevMLH
8297f989fd Bump version to 1.6.4.0 and update changelog for new features; modify select classes in config page and settings popup
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 58s
2026-02-03 19:45:38 +01:00
CodeDevMLH
636aaa2a4a Update manifest.json for release v1.6.3.0 [skip ci] 2026-02-03 18:33:25 +00:00
CodeDevMLH
5e70621e93 Update version to 1.6.3.0, modify checkbox layout, and adjust popup styles
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-03 19:32:34 +01:00
CodeDevMLH
0b4434c51c Update manifest.json for release v1.6.2.0 [skip ci] 2026-02-03 18:18:35 +00:00
CodeDevMLH
dd6583c055 Bump version to 1.6.2.0 and update changelog for new features
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-02-03 19:17:44 +01:00
CodeDevMLH
a0b0514159 Update manifest.json for release v1.6.1.0 [skip ci] 2026-02-03 18:08:59 +00:00
CodeDevMLH
e977c83e8f Bump version to 1.6.1.0 in project file and manifest
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-02-03 19:08:07 +01:00
CodeDevMLH
e281f5c579 test other ui design 2026-02-03 19:08:02 +01:00
CodeDevMLH
e6b646e478 Update .gitignore to include test-site-new.html [skip CI] 2026-02-03 18:50:22 +01:00
CodeDevMLH
8d6bc12fa4 Update manifest.json for release v1.6.0.0 [skip ci] 2026-02-03 17:49:06 +00:00
CodeDevMLH
f036e748da Add client-side toggle option for seasonal settings
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-02-03 18:48:11 +01:00
CodeDevMLH
0177a7caea Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 22:40:50 +00:00
CodeDevMLH
c45e9f6156 Auto-Update MediaBar Enhanced to v1.2.3.7
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-01-28 22:40:00 +00:00
CodeDevMLH
8dc9b9f157 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 21:32:51 +00:00
CodeDevMLH
e73c6c14bb Auto-Update MediaBar Enhanced to v1.2.3.6
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-01-28 21:32:01 +00:00
CodeDevMLH
aa1c60f9ce Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 20:22:17 +00:00
CodeDevMLH
d1e668bcff Auto-Update MediaBar Enhanced to v1.2.3.5
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-28 20:21:27 +00:00
CodeDevMLH
0454d43f32 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 01:10:34 +00:00
CodeDevMLH
61b3b51139 Auto-Update MediaBar Enhanced to v1.2.3.4
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-01-28 01:09:45 +00:00
CodeDevMLH
60d1a546a2 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 01:06:40 +00:00
CodeDevMLH
669933d270 Auto-Update MediaBar Enhanced to v1.2.3.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-28 01:05:53 +00:00
CodeDevMLH
053f0ccfa7 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 00:46:16 +00:00
CodeDevMLH
60e998fc7f Auto-Update MediaBar Enhanced to v1.2.3.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-28 00:45:27 +00:00
CodeDevMLH
3aa631198a Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 00:31:33 +00:00
CodeDevMLH
b1876d655e Auto-Update MediaBar Enhanced to v1.2.3.2
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-01-28 00:30:43 +00:00
CodeDevMLH
9a9f89c1fc Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-28 00:18:24 +00:00
CodeDevMLH
2fb41f6442 Auto-Update MediaBar Enhanced to v1.2.3.1
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-01-28 00:17:35 +00:00
CodeDevMLH
4ab949e6d7 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-27 23:55:34 +00:00
CodeDevMLH
7d1024c917 Auto-Update MediaBar Enhanced to v1.2.3.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-01-27 23:54:49 +00:00
CodeDevMLH
c2e3d55110 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-24 22:54:52 +00:00
CodeDevMLH
c6503a90bb Auto-Update MediaBar Enhanced to v1.2.2.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-01-24 22:54:01 +00:00
CodeDevMLH
b70787d5ec Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-22 23:51:52 +00:00
CodeDevMLH
a9eb8113a6 Auto-Update MediaBar Enhanced to v1.2.1.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-22 23:51:03 +00:00
CodeDevMLH
aaf15d8934 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-21 23:12:11 +00:00
CodeDevMLH
ec298ebde0 bump version to 1.5.1.0 in project file
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 53s
2026-01-22 00:11:19 +01:00
CodeDevMLH
bb108d0f49 Update manifest.json for release v1.5.1.0 [skip ci] 2026-01-21 22:53:15 +00:00
MLH
f271e1715d Merge pull request 'fix: snowfall effect sometimes not scaling on window resize, which leads to clustering and rain effect of snowflakes' (#1) from dev into main
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 57s
Reviewed-on: #1
2026-01-21 23:52:18 +01:00
CodeDevMLH
bd0e2779e5 fix: snowfall effect sometimes not scaling on window resize, which leads to clustering and rain effect of snowflakes
All checks were successful
🏗️ Build Plugin / build (push) Successful in 53s
🏗️ Build Plugin / build (pull_request) Successful in 58s
2026-01-21 23:51:23 +01:00
CodeDevMLH
53a1682868 Enhance descriptions and overviews in manifest.json for Seasonals and Media Bar Enhanced plugins [skip ci] 2026-01-09 00:32:40 +01:00
CodeDevMLH
a7df2fd832 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-08 23:31:54 +00:00
CodeDevMLH
c56cde860b Auto-Update MediaBar Enhanced to v1.2.0.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-08 23:31:04 +00:00
CodeDevMLH
59211e27c6 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-08 23:15:57 +00:00
CodeDevMLH
a2b1179353 Auto-Update MediaBar Enhanced to v1.2.0.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 47s
2026-01-08 23:15:12 +00:00
CodeDevMLH
c7f34ec92f Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-08 22:16:58 +00:00
CodeDevMLH
4c011cf560 Auto-Update MediaBar Enhanced to v1.2.0.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 48s
2026-01-08 22:16:11 +00:00
CodeDevMLH
e5f78c711d Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-08 15:27:46 +00:00
CodeDevMLH
98a536315b Auto-Update MediaBar Enhanced to v1.1.2.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-01-08 15:27:00 +00:00
CodeDevMLH
01343848e3 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-08 14:55:53 +00:00
CodeDevMLH
113e7dd0f7 Auto-Update MediaBar Enhanced to v1.1.1.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-01-08 14:55:04 +00:00
CodeDevMLH
1bc4176771 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-08 02:16:44 +00:00
CodeDevMLH
b091e5592d Auto-Update MediaBar Enhanced to v1.1.0.0
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-01-08 02:15:57 +00:00
CodeDevMLH
cb2d86340e Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 23:34:26 +00:00
CodeDevMLH
57a92e94de Remove build.yaml configuration file for Seasonals plugin
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 57s
2026-01-07 00:33:36 +01:00
CodeDevMLH
30bc8bef39 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 23:27:26 +00:00
CodeDevMLH
2d237063a3 Auto-Update MediaBar Enhanced to v1.0.0.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 54s
2026-01-06 23:26:36 +00:00
CodeDevMLH
049a5075b5 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 23:18:28 +00:00
CodeDevMLH
cb2e9c4b07 Auto-Update MediaBar Enhanced to v1.0.0.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-06 23:17:39 +00:00
CodeDevMLH
5a39f85082 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 23:13:12 +00:00
CodeDevMLH
b863d201b9 Auto-Update MediaBar Enhanced to v1.0.0.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-06 23:12:24 +00:00
CodeDevMLH
78b50b41c2 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 23:11:24 +00:00
CodeDevMLH
0ba1545fd6 test
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 49s
2026-01-07 00:10:33 +01:00
CodeDevMLH
16c4e0f29b fix link 2026-01-07 00:09:41 +01:00
CodeDevMLH
3581b8cbb2 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 22:57:27 +00:00
CodeDevMLH
7184c93f6f Auto-Update MediaBar Enhanced to v1.0.0.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-01-06 22:56:39 +00:00
CodeDevMLH
0969f0238f Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 22:45:32 +00:00
CodeDevMLH
12f868d3f9 test ..
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-01-06 23:44:42 +01:00
CodeDevMLH
dc7be56807 Merge branch 'main' of ssh://git.mahom03-spacecloud.de:44322/CodeDevMLH/Jellyfin-Seasonals-Plugin 2026-01-06 23:41:57 +01:00
CodeDevMLH
cb60690e6b fix log name 2026-01-06 23:41:55 +01:00
CodeDevMLH
e0397bb2e8 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 22:40:15 +00:00
CodeDevMLH
822bcafd11 Auto-Update MediaBar Enhanced to v1.0.0.3
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 50s
2026-01-06 22:39:29 +00:00
CodeDevMLH
429d96c816 Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 22:37:52 +00:00
CodeDevMLH
e948055f0f test
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 51s
2026-01-06 23:37:01 +01:00
CodeDevMLH
5129d46163 fix remote repo 2026-01-06 23:36:39 +01:00
CodeDevMLH
660f7142ef Update manifest.json for release v1.5.0.0 [skip ci] 2026-01-06 22:33:07 +00:00
CodeDevMLH
9f657588d8 fix changelog
All checks were successful
Auto Release Plugin / build-and-release (push) Successful in 52s
2026-01-06 23:32:16 +01:00
CodeDevMLH
0457f1a764 Merge branch 'main' of ssh://git.mahom03-spacecloud.de:44322/CodeDevMLH/Jellyfin-Seasonals-Plugin 2026-01-06 23:30:37 +01:00
CodeDevMLH
f196c6c296 fix url 2026-01-06 23:30:36 +01:00
14 changed files with 613 additions and 136 deletions

View File

@@ -40,6 +40,11 @@ jobs:
echo "Detected Version: $VERSION" echo "Detected Version: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "TARGET_ABI=$TARGET_ABI" >> $GITHUB_ENV
# Also export GUID for later use
PLUGIN_GUID=$(jq -r '.[0].guid' manifest.json)
echo "PLUGIN_GUID=$PLUGIN_GUID" >> $GITHUB_ENV
# Escape newlines in changelog for GITHUB_ENV # Escape newlines in changelog for GITHUB_ENV
echo "CHANGELOG<<EOF" >> $GITHUB_ENV echo "CHANGELOG<<EOF" >> $GITHUB_ENV
@@ -71,7 +76,7 @@ jobs:
REPO_OWNER="${{ github.repository_owner }}" REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}" REPO_NAME="${{ github.event.repository.name }}"
VERSION="${{ env.VERSION }}" VERSION="${{ env.VERSION }}"
DOWNLOAD_URL="https://git.mahom03-spacecloud.de/${{ github.repository }}/releases/download/v$VERSION/Jellyfin.Plugin.Seasonals.zip" DOWNLOAD_URL="https://git.mahom03-spacecloud.de/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.Seasonals.zip"
echo "Updating manifest.json with:" echo "Updating manifest.json with:"
echo "Hash: ${{ env.ZIP_HASH }}" echo "Hash: ${{ env.ZIP_HASH }}"
@@ -115,11 +120,14 @@ jobs:
run: | run: |
cd central-manifest cd central-manifest
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
# 1. Get info from previous steps # 1. Get info from previous steps
VERSION="${{ env.VERSION }}" VERSION="${{ env.VERSION }}"
HASH="${{ env.ZIP_HASH }}" HASH="${{ env.ZIP_HASH }}"
TIME="${{ env.BUILD_TIME }}" TIME="${{ env.BUILD_TIME }}"
DOWNLOAD_URL="https://git.mahom03-spacecloud.de/${{ github.repository }}/releases/download/v$VERSION/Jellyfin.Plugin.Seasonals.zip" DOWNLOAD_URL="https://git.mahom03-spacecloud.de/$REPO_OWNER/$REPO_NAME/releases/download/v$VERSION/Jellyfin.Plugin.Seasonals.zip"
# 2. Get info from env # 2. Get info from env
PLUGIN_GUID="${{ env.PLUGIN_GUID }}" PLUGIN_GUID="${{ env.PLUGIN_GUID }}"
@@ -171,7 +179,7 @@ jobs:
# Check if there are changes # Check if there are changes
if [[ -n $(git status -s) ]]; then if [[ -n $(git status -s) ]]; then
git add manifest.json git add manifest.json
git commit -m "Auto-Update MediaBar Enhanced to v${{ env.VERSION }}" git commit -m "Auto-Update Seasonals to v${{ env.VERSION }}"
git push git push
else else
echo "No changes to central manifest." echo "No changes to central manifest."

View File

@@ -39,6 +39,11 @@ jobs:
echo "Detected Version: $VERSION" echo "Detected Version: $VERSION"
echo "VERSION=$VERSION" >> $GITHUB_ENV echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "TARGET_ABI=$TARGET_ABI" >> $GITHUB_ENV
# Also export GUID for later use
PLUGIN_GUID=$(jq -r '.[0].guid' manifest.json)
echo "PLUGIN_GUID=$PLUGIN_GUID" >> $GITHUB_ENV
# Escape newlines in changelog for GITHUB_ENV # Escape newlines in changelog for GITHUB_ENV
echo "CHANGELOG<<EOF" >> $GITHUB_ENV echo "CHANGELOG<<EOF" >> $GITHUB_ENV
@@ -113,6 +118,9 @@ jobs:
run: | run: |
cd central-manifest cd central-manifest
REPO_OWNER="${{ github.repository_owner }}"
REPO_NAME="${{ github.event.repository.name }}"
# 1. Get info from previous steps # 1. Get info from previous steps
VERSION="${{ env.VERSION }}" VERSION="${{ env.VERSION }}"
HASH="${{ env.ZIP_HASH }}" HASH="${{ env.ZIP_HASH }}"
@@ -169,7 +177,7 @@ jobs:
# Check if there are changes # Check if there are changes
if [[ -n $(git status -s) ]]; then if [[ -n $(git status -s) ]]; then
git add manifest.json git add manifest.json
git commit -m "Auto-Update MediaBar Enhanced to v${{ env.VERSION }}" git commit -m "Auto-Update Seasonals to v${{ env.VERSION }}"
git push git push
else else
echo "No changes to central manifest." echo "No changes to central manifest."

1
.gitignore vendored
View File

@@ -5,4 +5,5 @@ obj/
artifacts artifacts
test-site.html test-site.html
test-site-new.html
RELEASE_GUIDE.md RELEASE_GUIDE.md

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>
@@ -40,7 +40,7 @@
</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">
<option value="none">None</option> <option value="none">None</option>
<option value="snowflakes">Snowflakes</option> <option value="snowflakes">Snowflakes</option>
<option value="snowfall">Snowfall</option> <option value="snowfall">Snowfall</option>
@@ -63,6 +63,15 @@
</select> </select>
<div class="fieldDescription">The season to display if automation is disabled.</div> <div class="fieldDescription">The season to display if automation is disabled.</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>
<br> <br>
<details> <details>
@@ -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.0.0</Version> <Version>1.6.4.0</Version>
<RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl> <RepositoryUrl>https://github.com/CodeDevMLH/Jellyfin-Seasonals</RepositoryUrl>
</PropertyGroup> </PropertyGroup>

View File

@@ -11,7 +11,7 @@ using Microsoft.Extensions.Logging;
namespace Jellyfin.Plugin.Seasonals; namespace Jellyfin.Plugin.Seasonals;
/// <summary> /// <summary>
/// The main plugin. /// The main plugin.
/// </summary> /// </summary>
public class SeasonalsPlugin : BasePlugin<PluginConfiguration>, IHasWebPages public class SeasonalsPlugin : BasePlugin<PluginConfiguration>, IHasWebPages
{ {

View File

@@ -60,6 +60,7 @@ observer.observe(document.body, {
attributes: true // observe changes to attributes (e.g. class changes) attributes: true // observe changes to attributes (e.g. class changes)
}); });
let resizeObserver; // Observer for resize events
function initializeCanvas() { function initializeCanvas() {
if (document.getElementById('snowfallCanvas')) { if (document.getElementById('snowfallCanvas')) {
@@ -78,8 +79,12 @@ function initializeCanvas() {
container.appendChild(canvas); container.appendChild(canvas);
ctx = canvas.getContext('2d'); ctx = canvas.getContext('2d');
// Initial resize
resizeCanvas(container); resizeCanvas(container);
window.addEventListener('resize', () => resizeCanvas(container));
// Initialize ResizeObserver
resizeObserver = new ResizeObserver(() => resizeCanvas(container));
resizeObserver.observe(container);
} }
function removeCanvas() { function removeCanvas() {
@@ -96,15 +101,37 @@ function removeCanvas() {
animationFrameIdSanta = null; animationFrameIdSanta = null;
console.log('Santa animation frame canceled'); console.log('Santa animation frame canceled');
} }
// Disconnect ResizeObserver
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
console.log('Canvas removed'); console.log('Canvas removed');
} }
} }
function resizeCanvas(container) { function resizeCanvas(container) {
if (!canvas) return; if (!canvas) return;
const oldWidth = canvas.width;
const oldHeight = canvas.height;
const rect = container.getBoundingClientRect(); const rect = container.getBoundingClientRect();
canvas.width = rect.width; canvas.width = rect.width;
canvas.height = rect.height; canvas.height = rect.height;
// Scale snowflakes positions if dimensions changed (to avoid clustering)
if (oldWidth > 0 && oldHeight > 0 && snowflakes.length > 0) {
const scaleX = canvas.width / oldWidth;
const scaleY = canvas.height / oldHeight;
snowflakes.forEach(flake => {
flake.x *= scaleX;
flake.y *= scaleY;
});
}
} }
function createSnowflakes(container) { function createSnowflakes(container) {
@@ -210,7 +237,7 @@ function reloadSantaGif() {
function animateSanta() { function animateSanta() {
const santa = document.querySelector('.santa'); const santa = document.querySelector('.santa');
function startAnimation() { function startAnimation() {
const santaHeight = santa.offsetHeight; const santaHeight = santa.offsetHeight;
if (santaHeight === 0) { if (santaHeight === 0) {

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,144 @@ 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: '0.3em',
boxShadow: '0 0 20px rgba(0,0,0,0.5)',
minWidth: '200px',
color: '#fff',
maxWidth: '250px'
});
const rect = anchorElement.getBoundingClientRect();
popup.style.top = `${rect.bottom + 10}px`;
popup.style.right = `${window.innerWidth - rect.right}px`;
popup.innerHTML = `
<div class="checkboxContainer checkboxContainer-withDescription" style="margin-bottom: 0.5em;">
<label class="emby-checkbox-label">
<input id="seasonal-enable-toggle" type="checkbox" is="emby-checkbox" class="emby-checkbox" />
<span class="checkboxLabel">Enable Seasonals</span>
</label>
</div>
<div class="selectContainer">
<label class="selectLabel" for="seasonal-theme-select" style="margin-bottom: 0.5em; display: block; color: inherit;">Force Theme</label>
<select is="emby-select" id="seasonal-theme-select" class="emby-select" style="width: 100%; padding: 0.5em; background-color: #333; border: 1px solid #444; color: #fff; border-radius: 4px;">
<option value="auto">Auto (Date Based)</option>
</select>
</div>
`;
// Populate Select Options
const themeSelect = popup.querySelector('#seasonal-theme-select');
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 Initial Values
const enabledCheckbox = popup.querySelector('#seasonal-enable-toggle');
enabledCheckbox.checked = getSavedSetting('seasonals-enabled', 'true') === 'true';
themeSelect.value = getSavedSetting('seasonals-theme', 'auto');
enabledCheckbox.addEventListener('change', (e) => {
setSavedSetting('seasonals-enabled', e.target.checked);
location.reload();
});
themeSelect.addEventListener('change', (e) => {
setSavedSetting('seasonals-theme', e.target.value);
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);
}
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

@@ -55,6 +55,7 @@ observer.observe(document.body, {
attributes: true // observe changes to attributes (e.g. class changes) attributes: true // observe changes to attributes (e.g. class changes)
}); });
let resizeObserver; // Observer for resize events
function initializeCanvas() { function initializeCanvas() {
if (document.getElementById('snowfallCanvas')) { if (document.getElementById('snowfallCanvas')) {
@@ -73,8 +74,12 @@ function initializeCanvas() {
container.appendChild(canvas); container.appendChild(canvas);
ctx = canvas.getContext('2d'); ctx = canvas.getContext('2d');
// Initial resize
resizeCanvas(container); resizeCanvas(container);
window.addEventListener('resize', () => resizeCanvas(container));
// Initialize ResizeObserver
resizeObserver = new ResizeObserver(() => resizeCanvas(container));
resizeObserver.observe(container);
} }
function removeCanvas() { function removeCanvas() {
@@ -86,15 +91,37 @@ function removeCanvas() {
animationFrameId = null; animationFrameId = null;
console.log('Animation frame canceled'); console.log('Animation frame canceled');
} }
// Disconnect ResizeObserver
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
console.log('Canvas removed'); console.log('Canvas removed');
} }
} }
function resizeCanvas(container) { function resizeCanvas(container) {
if (!canvas) return; if (!canvas) return;
const oldWidth = canvas.width;
const oldHeight = canvas.height;
const rect = container.getBoundingClientRect(); const rect = container.getBoundingClientRect();
canvas.width = rect.width; canvas.width = rect.width;
canvas.height = rect.height; canvas.height = rect.height;
// Scale snowflakes positions if dimensions changed (to avoid clustering)
if (oldWidth > 0 && oldHeight > 0 && snowflakes.length > 0) {
const scaleX = canvas.width / oldWidth;
const scaleY = canvas.height / oldHeight;
snowflakes.forEach(flake => {
flake.x *= scaleX;
flake.y *= scaleY;
});
}
} }
function createSnowflakes(container) { function createSnowflakes(container) {

View File

@@ -57,6 +57,7 @@ observer.observe(document.body, {
attributes: true // observe changes to attributes (e.g. class changes) attributes: true // observe changes to attributes (e.g. class changes)
}); });
let resizeObserver; // Observer for resize events
function initializeCanvas() { function initializeCanvas() {
if (document.getElementById('snowfallCanvas')) { if (document.getElementById('snowfallCanvas')) {
@@ -75,8 +76,12 @@ function initializeCanvas() {
container.appendChild(canvas); container.appendChild(canvas);
ctx = canvas.getContext('2d'); ctx = canvas.getContext('2d');
// Initial resize
resizeCanvas(container); resizeCanvas(container);
window.addEventListener('resize', () => resizeCanvas(container));
// Initialize ResizeObserver
resizeObserver = new ResizeObserver(() => resizeCanvas(container));
resizeObserver.observe(container);
} }
function removeCanvas() { function removeCanvas() {
@@ -88,15 +93,37 @@ function removeCanvas() {
animationFrameId = null; animationFrameId = null;
console.log('Animation frame canceled'); console.log('Animation frame canceled');
} }
// Disconnect ResizeObserver
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
console.log('Canvas removed'); console.log('Canvas removed');
} }
} }
function resizeCanvas(container) { function resizeCanvas(container) {
if (!canvas) return; if (!canvas) return;
const oldWidth = canvas.width;
const oldHeight = canvas.height;
const rect = container.getBoundingClientRect(); const rect = container.getBoundingClientRect();
canvas.width = rect.width; canvas.width = rect.width;
canvas.height = rect.height; canvas.height = rect.height;
// Scale snowflakes positions if dimensions changed (to avoid clustering)
if (oldWidth > 0 && oldHeight > 0 && snowflakes.length > 0) {
const scaleX = canvas.width / oldWidth;
const scaleY = canvas.height / oldHeight;
snowflakes.forEach(flake => {
flake.x *= scaleX;
flake.y *= scaleY;
});
}
} }
function createSnowflakes(container) { function createSnowflakes(container) {

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

@@ -1,15 +0,0 @@
---
name: "Seasonals"
guid: "ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4"
version: "1.3.4.0"
targetAbi: "10.11.0.0"
framework: "net9.0"
overview: "Seasonal effects for Jellyfin"
description: >
Adds seasonal effects like snow, leaves, etc. to the Jellyfin web interface.
category: "General"
owner: "CodeDevMLH"
artifacts:
- "Jellyfin.Plugin.Seasonals.dll"
changelog: >
Added Advanced Configuration UI for customizing individual seasonal effects.

View File

@@ -2,19 +2,35 @@
{ {
"guid": "ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4", "guid": "ef1e863f-cbb0-4e47-9f23-f0cbb1826ad4",
"name": "Seasonals", "name": "Seasonals",
"description": "Adds seasonal effects like snow, leaves, etc. to the Jellyfin web interface.", "description": "Adds atmospheric, animated seasonal effects like snow, falling leaves, hearts, fireworks and more. The plugin provides a settings page for configuration. If you do not have write permissions to the web folder, please also install the file-transformation plugin.",
"overview": "Seasonal effects for Jellyfin", "overview": "Atmospheric, configurable seasonal effects for the Jellyfin web interface",
"owner": "CodeDevMLH", "owner": "CodeDevMLH",
"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.4.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.4.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "0d33448d7aacf5dbc5e03f3bb712ff43",
"timestamp": "2026-02-03T18:46:35Z"
},
{
"version": "1.5.1.0",
"changelog": "- fix: snowfall effect sometimes not scaling on window resize, which leads to clustering and rain effect of snowflakes",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.5.1.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "a66b5c24c1f4bfff14bba5e93576bb80",
"timestamp": "2026-01-28T22:40:49Z"
},
{ {
"version": "1.5.0.0", "version": "1.5.0.0",
"changelog": "- Refactor SeasonalsPlugin: Simplify script injection logic", "changelog": "- Refactor script injection logic",
"targetAbi": "10.11.0.0", "targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.5.0.0/Jellyfin.Plugin.Seasonals.zip", "sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/Jellyfin-Seasonals-Plugin/releases/download/v1.5.0.0/Jellyfin.Plugin.Seasonals.zip",
"checksum": "fa174c764164b0524fb2e8f11ffbcace", "checksum": "ba8d3c358df3e0546b99113b43f54fea",
"timestamp": "2026-01-06T22:21:49Z" "timestamp": "2026-01-08T23:31:52Z"
}, },
{ {
"version": "1.4.1.0", "version": "1.4.1.0",
@@ -85,12 +101,132 @@
{ {
"guid": "d7e11d57-819b-4bdd-a88d-53c5f5560225", "guid": "d7e11d57-819b-4bdd-a88d-53c5f5560225",
"name": "Media Bar Enhanced", "name": "Media Bar Enhanced",
"description": "A jellyfin plugin to display a media bar (featured content) for jellyfin web.", "description": "A feature-rich fork of the original Media Bar script by MakD that brings your home screen to life.\n\n-> 100% Configurable via Web UI: Manage all features, lists, and settings effortlessly through the plugin configuration page.\n\nKey Highlights:\n- Cinematic Video Backdrops: Supports local & YouTube trailers (incl. SponsorBlock)\n- Custom Content: Curate your slideshow with specific Collections, Playlists, or seasonal events\n\nAdditional Features:\n- Full-width immersive mode\n- Smart resolution handling (up to 4K)\n- Full keyboard navigation & playback control\n- Wait-for-trailer options\n- Customizable pagination & animations\n\nIf you do not have write permissions to the web folder, please also install the file-transformation plugin.",
"overview": "Media Bar for Jellyfin", "overview": "Transforms your Jellyfin home screen with an immersive, fully configurable media slideshow featuring video backdrops.",
"owner": "CodeDevMLH", "owner": "CodeDevMLH",
"category": "General", "category": "General",
"imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png", "imageUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/raw/branch/main/logo.png",
"versions": [ "versions": [
{
"version": "1.2.3.7",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.\n- Update mediaBarEnhanced.js and mediaBarEnhanced.css with version 3.0.9 from original repo",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.7/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "fa1bf48cff159cc7dbf0aab48511a37c",
"timestamp": "2026-01-28T22:39:54Z"
},
{
"version": "1.2.3.6",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.6/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "da73bb490548c122906419d2762a2d00",
"timestamp": "2026-01-28T21:31:54Z"
},
{
"version": "1.2.3.5",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.5/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "b5efea79ec465522dad31e4ee5f1710c",
"timestamp": "2026-01-28T20:21:20Z"
},
{
"version": "1.2.3.4",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.4/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "4683f75e2df2590db663303bdd329ccd",
"timestamp": "2026-01-28T01:09:38Z"
},
{
"version": "1.2.3.3",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.3/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "3c18a84b2f59c86c130e91da83f980a2",
"timestamp": "2026-01-28T01:05:45Z"
},
{
"version": "1.2.3.2",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.2/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "4109a3ea10eb3145217b24ee8f8b37b5",
"timestamp": "2026-01-28T00:30:36Z"
},
{
"version": "1.2.3.1",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.1/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "e73029ac767e24d36742a27678758b6f",
"timestamp": "2026-01-28T00:17:28Z"
},
{
"version": "1.2.3.0",
"changelog": "- Fixes the issue where buttons were cut off on smaller screens such as on S24/S25.",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.3.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "ae6f34bee76f9d7873964a71ca191bf3",
"timestamp": "2026-01-27T23:54:42Z"
},
{
"version": "1.2.2.0",
"changelog": "- Fixes issues with persistent slides-container visibility",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.2.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "3362f93815845c4e85b66b31bcd0f52c",
"timestamp": "2026-01-24T22:53:55Z"
},
{
"version": "1.2.1.0",
"changelog": "- Update mediaBarEnhanced.js and mediaBarEnhanced.css with version 3.0.8 from original repo",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.1.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "70defc1fb29a17ff4c9362bf7bdc53b5",
"timestamp": "2026-01-22T23:50:56Z"
},
{
"version": "1.2.0.0",
"changelog": "- Add video quality preference setting (Auto / 1080p / Highres)\n- Set preferred video quality on YouTube player based on setting",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.2.0.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "0b6379f68990026240d97fe8f77fbef1",
"timestamp": "2026-01-08T23:30:58Z"
},
{
"version": "1.1.2.0",
"changelog": "- Add method to resume video playback when slideshow is active",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.1.2.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "a0e8ff5e59b22a1bdedc916cd5e1c16a",
"timestamp": "2026-01-08T15:26:55Z"
},
{
"version": "1.1.1.0",
"changelog": "- Add method to pause all video playback when navigating away from home screen",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.1.1.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "09da95fc561b11191d23a5cfa30ea731",
"timestamp": "2026-01-08T14:54:57Z"
},
{
"version": "1.1.0.0",
"changelog": "- 'custom media IDs' setting is now enabled by default (no input --> random selection)\n- improve GUID handling in slideshow manager to handle seperator and description",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.1.0.0/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "32305d72b8d704acf8eef0c22277fee9",
"timestamp": "2026-01-08T02:15:50Z"
},
{
"version": "1.0.0.3",
"changelog": "fixes",
"targetAbi": "10.11.0.0",
"sourceUrl": "https://git.mahom03-spacecloud.de/CodeDevMLH/jellyfin-plugin-media-bar-enhanced/releases/download/v1.0.0.3/Jellyfin.Plugin.MediaBarEnhanced.zip",
"checksum": "e6180c42836069029072e96ac4860c42",
"timestamp": "2026-01-06T23:26:29Z"
},
{ {
"version": "1.0.0.2", "version": "1.0.0.2",
"changelog": "fixes", "changelog": "fixes",