mirror of
https://github.com/IAmParadox27/jellyfin-plugin-media-bar.git
synced 2026-11-13 08:54:19 +01:00
Merge branch 'upstream'
# Conflicts: # README.md # slideshowpure.js
This commit is contained in:
commit
0ccd183af6
3 files changed
+247
-275
No files matched your search
@@ -27,7 +27,7 @@ As with a lot of devs, I am very momentum based in my personal life coding and t
|
||||
### Prerequisites
|
||||
- This plugin is based on Jellyfin Version `10.10.7`
|
||||
- The following plugins are required to also be installed, please following their installation guides:
|
||||
- File Transformation (https://github.com/IAmParadox27/jellyfin-plugin-file-transformation) at least v2.2.1.0
|
||||
- File Transformation (https://github.com/IAmParadox27/jellyfin-plugin-file-transformation) at least v2.2.1.0
|
||||
|
||||
### Installation
|
||||
1. Add `https://www.iamparadox.dev/jellyfin/plugins/manifest.json` to your plugin repositories.
|
||||
@@ -62,128 +62,64 @@ Credits for this plugin go to @MakD for his original work and to @BobHasNoSoul a
|
||||
|
||||
<details>
|
||||
<summary>Original README.md from MakD</summary>
|
||||
|
||||
## TODO - Design changes upcoming next release
|
||||
|
||||
Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar).
|
||||
# Jellyfin-Media-Bar - Now with Play Now Function
|
||||
|
||||
Here I present my version of the same with some code improvements, loading optimizations, and Security Enhancements. Works best with the [Zombie theme](https://github.com/MakD/zombie-release) (_Shameless Plug_), but it fits with every other theme the creators have put their hard work in. You might've to edit the color accents in the CSS to match yours.
|
||||
> [!NOTE]
|
||||
> The Media Bar is currently partly compatible with the Jellyfin 10.11.x update. We’re aware of the changes required to make it fully compatible and will be addressing them soon.
|
||||
>
|
||||
> I’ll be away on vacation for a short while, so there will be a temporary delay in releasing the next update. I kindly ask that you refrain from opening new bug reports related to 10.11.x compatibility during this period — we’ve got it on our radar.
|
||||
>
|
||||
> Thank you for your patience and understanding! The fixes will be rolled out shortly after I return.
|
||||
|
||||
|
||||
**IMP UPDATE — We have dropped support for the normal CSS version (for now). _(It still works, but there will be no further updates till the fullscreen mode is stabilized)_**
|
||||
|
||||
The fullscreen version has a new look (in beta), and support for different screen sizes has been added. For any visual goof-ups, please open a bug report, including the device being used and whether it is encountered in portrait or landscape mode.
|
||||
|
||||
|
||||
Thanks to the Man, the Legend [BobHasNoSoul](https://github.com/BobHasNoSoul) for his work on the [jellyfinfeatured](https://github.com/BobHasNoSoul/jellyfin-featured) and [SethBacon](https://forum.jellyfin.org/u-sethbacon) and [TedHinklater](https://github.com/tedhinklater) for their take on the [Jellyfin-Featured-Content-Bar](https://github.com/tedhinklater/Jellyfin-Featured-Content-Bar).
|
||||
|
||||
Here I present my version with some code improvements, loading optimizations, and security enhancements. Works best with the [Zombie theme](https://github.com/MakD/zombie-release) (_Shameless Plug_ `@import url(https://cdn.jsdelivr.net/gh/MakD/zombie-release@latest/zombie_revived.css);`, visit the repo for more color schemes).
|
||||
|
||||
|
||||
> <ins>**Before Installing, please take a backup of your index.html file**<ins>
|
||||
|
||||
<details>
|
||||
<summary> Desktop Layout </summary>
|
||||
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary> Mobile Layout </summary>
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
</details>
|
||||
|
||||
> <ins>**Before Installing, please take a backup of your index.html and home-html.xxxxxx.chunk.js files**<ins>
|
||||
|
||||
# Prepping the Environment
|
||||
|
||||
<details>
|
||||
|
||||
<summary> Steps </summary>
|
||||
|
||||
1. Create a folder `avatars` in your `jellyfin-web` folder. (Usually in C:\Program Files\Jellyfin\Server)
|
||||
2. Download the files `slideshowpure.js` and `slideshowpure.css`
|
||||
3. Paste them inside the avatars folder created, and you are ready to venture down the rabbit hole.
|
||||
|
||||
</details>
|
||||
|
||||
# Prepping the files
|
||||
<details>
|
||||
|
||||
|
||||
<summary>index.html</summary>
|
||||
|
||||
1. Navigate to your `jellyfin-web` folder and search for the file index.html. (you can use any code editor, just remember to open with administrator privileges.
|
||||
2. Search for `</body></html>`
|
||||
3. Just before the `</body`, plug the below code
|
||||
1. Navigate to your `jellyfin-web` folder and search for the file index.html. (you can use any code editor, just remember to open with administrator privileges.
|
||||
2. Search for `</head>`
|
||||
3. Just before the `</head>`, plug the below code
|
||||
```
|
||||
|
||||
<script>
|
||||
function saveCredentialsToSessionStorage(credentials) {
|
||||
try {
|
||||
sessionStorage.setItem(
|
||||
"json-credentials",
|
||||
JSON.stringify(credentials)
|
||||
);
|
||||
console.log("Credentials saved to sessionStorage.");
|
||||
} catch (error) {
|
||||
console.error("Error saving credentials:", error);
|
||||
}
|
||||
}
|
||||
function saveApiKey(apiKey) {
|
||||
try {
|
||||
sessionStorage.setItem("api-key", apiKey);
|
||||
console.log("API key saved to sessionStorage.");
|
||||
} catch (error) {
|
||||
console.error("Error saving API key:", error);
|
||||
}
|
||||
}
|
||||
(function () {
|
||||
var originalConsoleLog = console.log;
|
||||
console.log = function (message) {
|
||||
originalConsoleLog.apply(console, arguments);
|
||||
if (
|
||||
typeof message === "string" &&
|
||||
message.startsWith("Stored JSON credentials:")
|
||||
) {
|
||||
try {
|
||||
var jsonString = message.substring(
|
||||
"Stored JSON credentials: ".length
|
||||
);
|
||||
var credentials = JSON.parse(jsonString);
|
||||
saveCredentialsToSessionStorage(credentials);
|
||||
} catch (error) {
|
||||
console.error("Error parsing credentials:", error);
|
||||
}
|
||||
}
|
||||
if (
|
||||
typeof message === "string" &&
|
||||
message.startsWith("opening web socket with url:")
|
||||
) {
|
||||
try {
|
||||
var url = message.split("url:")[1].trim();
|
||||
var urlParams = new URL(url).searchParams;
|
||||
var apiKey = urlParams.get("api_key");
|
||||
if (apiKey) {
|
||||
saveApiKey(apiKey);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error extracting API key:", error);
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
</script>
|
||||
<link rel="preload" href="/web/avatars/slideshowpure.css" as="style" />
|
||||
<link rel="stylesheet" href="/web/avatars/slideshowpure.css" />
|
||||
<script defer src="/web/avatars/slideshowpure.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/MakD/Jellyfin-Media-Bar@latest/slideshowpure.css" />
|
||||
<script async src="https://cdn.jsdelivr.net/gh/MakD/Jellyfin-Media-Bar@latest/slideshowpure.js"></script>
|
||||
```
|
||||
</details>
|
||||
|
||||
<details>
|
||||
|
||||
<summary>home-html.xxxxxx.chunk.js</summary>
|
||||
|
||||
1. Similarly, search for `home-html` in the `jellyfin-web` directory. You should be able to see a file named `home-html.xxxxxx.chunk.js` with random numbers in place of the `xxxx`. Open it with any code editor with administrator privileges.
|
||||
2. Search for `id="homeTab" data-index="0">`
|
||||
3. Right after the `>`, paste the code block `<div id="slides-container"></div><script>slidesInit()</script>`
|
||||
|
||||
</details>
|
||||
|
||||
And that is it. Hard refresh your web page (CTRL+Shift+R) twice, and Profit!
|
||||
|
||||
# Want a Custom List to be showcased instead of random items??
|
||||
|
||||
No worries this got you covered.
|
||||
No worries this got you covered.
|
||||
|
||||
## Steps
|
||||
|
||||
@@ -200,4 +136,27 @@ ItemID4
|
||||
ItemID5
|
||||
```
|
||||
The next time it loads, it will display these items.
|
||||
|
||||
# Uninstall the Bar
|
||||
|
||||
<details>
|
||||
|
||||
<summary> Roll Back </summary>
|
||||
|
||||
Restore the `index.html` file / remove the lines added and you are good to go!!!
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
|
||||
This project is licensed under a DBAD license prohibiting any commercial use or redistribution.
|
||||
All modifications must be contributed back to this repository.
|
||||
Attribution to the original author (MakD) is required in any use or derivative work.
|
||||
|
||||
Please take a look at the [LICENSE](LICENSE) file for full terms.
|
||||
|
||||
</details>
|
||||
+73
-37
@@ -1,11 +1,12 @@
|
||||
/*
|
||||
* Jellyfin Slideshow by M0RPH3US v3.0.0
|
||||
* Jellyfin Slideshow by M0RPH3US v3.0.4
|
||||
*/
|
||||
|
||||
@import url(https://fonts.googleapis.com/css2?family=Archivo+Narrow:ital,wght@0,400..700;1,400..700&display=swap);
|
||||
|
||||
.backdrop.animate {
|
||||
animation: frostedGlass 1.2s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
animation:
|
||||
frostedGlass 1.2s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
kenBurnsZoomIn 10s ease-out forwards;
|
||||
}
|
||||
|
||||
@@ -75,7 +76,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bar-loading h1 img {
|
||||
.bar-loading h1 div {
|
||||
width: 250px;
|
||||
max-height: 250px;
|
||||
display: block;
|
||||
@@ -117,13 +118,17 @@
|
||||
.backdrop.low-quality {
|
||||
filter: blur(0.5px);
|
||||
transform: scale(1.01);
|
||||
transition: filter 0.3s ease-in-out, transform 0.3s ease-in-out;
|
||||
transition:
|
||||
filter 0.3s ease-in-out,
|
||||
transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.backdrop.high-quality {
|
||||
filter: blur(0);
|
||||
transform: scale(1);
|
||||
transition: filter 0.3s ease-in-out, transform 0.3s ease-in-out;
|
||||
transition:
|
||||
filter 0.3s ease-in-out,
|
||||
transform 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.logo.low-quality {
|
||||
@@ -174,6 +179,42 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.pause-button {
|
||||
position: absolute;
|
||||
top: 5rem;
|
||||
right: 0.8rem;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
z-index: 10;
|
||||
opacity: 0.3;
|
||||
transition: opacity 0.3s ease;
|
||||
text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.pause-button i {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.pause-button:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
@media (max-width: 1599px) {
|
||||
.pause-button {
|
||||
top: 8rem;
|
||||
}
|
||||
|
||||
.pause-button i {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.pause-button i {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.dots-container {
|
||||
position: absolute;
|
||||
top: calc(50% + 18vh);
|
||||
@@ -195,7 +236,8 @@
|
||||
background-color: #cecece99;
|
||||
border-radius: 50%;
|
||||
transform-origin: center;
|
||||
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transition:
|
||||
transform 0.5s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
background-color 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
@@ -330,6 +372,7 @@
|
||||
width: auto;
|
||||
object-fit: contain;
|
||||
filter: brightness(1.5);
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.plot-container {
|
||||
@@ -356,6 +399,10 @@
|
||||
}
|
||||
|
||||
.genre {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: calc(50% + 4vh);
|
||||
left: 4vw;
|
||||
@@ -363,6 +410,7 @@
|
||||
color: #fff;
|
||||
font-family: "Archivo Narrow", sans-serif;
|
||||
z-index: 5;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
@@ -390,6 +438,7 @@
|
||||
font-weight: 700;
|
||||
gap: 6px;
|
||||
-webkit-tap-highlight-color: #fff0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.detail-button {
|
||||
@@ -471,63 +520,54 @@
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.rating-value {
|
||||
.misc-info {
|
||||
font-family: "Archivo Narrow", sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
z-index: 5;
|
||||
position: relative;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.runTime {
|
||||
font-family: "Archivo Narrow", sans-serif;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tomato-rating {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 0;
|
||||
/* Star Icon Styling */
|
||||
.community-rating-star {
|
||||
color: #f2b01e;
|
||||
font-size: 1.4em;
|
||||
height: auto !important;
|
||||
margin-right: 0.125em;
|
||||
width: auto !important;
|
||||
}
|
||||
|
||||
.imdb-logo {
|
||||
margin-right: 1px;
|
||||
.star-rating-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tomato-logo {
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
/* Rotten Tomatoes Critic Rating Styles */
|
||||
|
||||
.critic-rating {
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
display: flex;
|
||||
min-height: 1.2em;
|
||||
gap: 0.25em;
|
||||
}
|
||||
|
||||
.critic-logo {
|
||||
margin-right: 5px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/**/
|
||||
|
||||
.age-rating {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
border-radius: 5px;
|
||||
background: rgb(255 255 255 / 0.8);
|
||||
color: #000;
|
||||
@@ -542,16 +582,12 @@
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
flex-wrap: wrap;
|
||||
align-content: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.separator-icon {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
font-size: 10px;
|
||||
color: aquamarine;
|
||||
}
|
||||
|
||||
+117
-140
@@ -1,14 +1,10 @@
|
||||
/*
|
||||
* Jellyfin Slideshow by M0RPH3US v3.0.1
|
||||
* Jellyfin Slideshow by M0RPH3US v3.0.4
|
||||
*/
|
||||
|
||||
//Core Module Configuration
|
||||
const CONFIG = {
|
||||
IMAGE_SVG: {
|
||||
imdbLogo:
|
||||
'<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 575 289.83" width="33" height="32.83"><defs><path d="M575 24.91C573.44 12.15 563.97 1.98 551.91 0C499.05 0 76.18 0 23.32 0C10.11 2.17 0 14.16 0 28.61C0 51.84 0 237.64 0 260.86C0 276.86 12.37 289.83 27.64 289.83C79.63 289.83 495.6 289.83 547.59 289.83C561.65 289.83 573.26 278.82 575 264.57C575 216.64 575 48.87 575 24.91Z" id="d1pwhf9wy2"></path><path d="M69.35 58.24L114.98 58.24L114.98 233.89L69.35 233.89L69.35 58.24Z" id="g5jjnq26yS"></path><path d="M201.2 139.15C197.28 112.38 195.1 97.5 194.67 94.53C192.76 80.2 190.94 67.73 189.2 57.09C185.25 57.09 165.54 57.09 130.04 57.09L130.04 232.74L170.01 232.74L170.15 116.76L186.97 232.74L215.44 232.74L231.39 114.18L231.54 232.74L271.38 232.74L271.38 57.09L211.77 57.09L201.2 139.15Z" id="i3Prh1JpXt"></path><path d="M346.71 93.63C347.21 95.87 347.47 100.95 347.47 108.89C347.47 115.7 347.47 170.18 347.47 176.99C347.47 188.68 346.71 195.84 345.2 198.48C343.68 201.12 339.64 202.43 333.09 202.43C333.09 190.9 333.09 98.66 333.09 87.13C338.06 87.13 341.45 87.66 343.25 88.7C345.05 89.75 346.21 91.39 346.71 93.63ZM367.32 230.95C372.75 229.76 377.31 227.66 381.01 224.67C384.7 221.67 387.29 217.52 388.77 212.21C390.26 206.91 391.14 196.38 391.14 180.63C391.14 174.47 391.14 125.12 391.14 118.95C391.14 102.33 390.49 91.19 389.48 85.53C388.46 79.86 385.93 74.71 381.88 70.09C377.82 65.47 371.9 62.15 364.12 60.13C356.33 58.11 343.63 57.09 321.54 57.09C319.27 57.09 307.93 57.09 287.5 57.09L287.5 232.74L342.78 232.74C355.52 232.34 363.7 231.75 367.32 230.95Z" id="a4ov9rRGQm"></path><path d="M464.76 204.7C463.92 206.93 460.24 208.06 457.46 208.06C454.74 208.06 452.93 206.98 452.01 204.81C451.09 202.65 450.64 197.72 450.64 190C450.64 185.36 450.64 148.22 450.64 143.58C450.64 135.58 451.04 130.59 451.85 128.6C452.65 126.63 454.41 125.63 457.13 125.63C459.91 125.63 463.64 126.76 464.6 129.03C465.55 131.3 466.03 136.15 466.03 143.58C466.03 146.58 466.03 161.58 466.03 188.59C465.74 197.84 465.32 203.21 464.76 204.7ZM406.68 231.21L447.76 231.21C449.47 224.5 450.41 220.77 450.6 220.02C454.32 224.52 458.41 227.9 462.9 230.14C467.37 232.39 474.06 233.51 479.24 233.51C486.45 233.51 492.67 231.62 497.92 227.83C503.16 224.05 506.5 219.57 507.92 214.42C509.34 209.26 510.05 201.42 510.05 190.88C510.05 185.95 510.05 146.53 510.05 141.6C510.05 131 509.81 124.08 509.34 120.83C508.87 117.58 507.47 114.27 505.14 110.88C502.81 107.49 499.42 104.86 494.98 102.98C490.54 101.1 485.3 100.16 479.26 100.16C474.01 100.16 467.29 101.21 462.81 103.28C458.34 105.35 454.28 108.49 450.64 112.7C450.64 108.89 450.64 89.85 450.64 55.56L406.68 55.56L406.68 231.21Z" id="fk968BpsX"></path></defs><g><g><g><use xlink:href="#d1pwhf9wy2" opacity="1" fill="#f6c700" fill-opacity="1"></use><g><use xlink:href="#d1pwhf9wy2" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#g5jjnq26yS" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#g5jjnq26yS" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#i3Prh1JpXt" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#i3Prh1JpXt" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a4ov9rRGQm" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#a4ov9rRGQm" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#fk968BpsX" opacity="1" fill="#000000" fill-opacity="1"></use><g><use xlink:href="#fk968BpsX" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>',
|
||||
tomatoLogo:
|
||||
'<svg xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid meet" viewBox="0 0 106.25 140" width="18" height="20"><path fill="#fa3106" d="M2.727 39.537c-.471-21.981 100.88-25.089 100.88-.42L92.91 117.56c-7.605 26.86-72.064 27.007-79.07.21z"/><g fill="#fff"><path d="M8.809 51.911l9.018 66.639c3.472 4.515 8.498 7.384 9.648 8.022l-6.921-68.576c-3.498-1.41-9.881-4.579-11.745-6.083zM28.629 59.776l5.453 68.898c4.926 2.652 11.04 3.391 15.73 3.566l-1.258-70.366c-3.414-.024-13.82-.642-19.925-2.098zM97.632 52.121l-9.019 66.643c-3.472 4.515-8.498 7.384-9.647 8.022l6.92-68.583c3.5-1.41 9.882-4.579 11.746-6.082zM77.812 59.986l-5.453 68.898c-4.926 2.652-11.04 3.391-15.73 3.566l1.258-70.366c3.414-.024 13.82-.642 19.925-2.098z"/></g><g fill="#ffd600"><circle cx="13.213" cy="31.252" r="6.816"/><circle cx="22.022" cy="27.687" r="6.607"/><circle cx="30.359" cy="19.769" r="5.925"/><circle cx="34.973" cy="15.155" r="6.03"/><circle cx="45.093" cy="17.095" r="4.929"/><circle cx="51.123" cy="9.597" r="6.24"/><circle cx="61.19" cy="9.387" r="6.554"/><circle cx="67.954" cy="13.635" r="4.929"/><circle cx="76.081" cy="17.672" r="5.925"/><circle cx="78.913" cy="22.706" r="4.352"/><circle cx="83.475" cy="26.324" r="5.243"/><circle cx="88.194" cy="34.398" r="5.768"/><path d="M87.355 35.447c5.79 2.799 1.352-2.213 10.696 2.097-9.574 15.338-74.774 16.892-90.291.525l-.21-3.985L38.59 16.99l22.863-6.606 15.52 9.962z"/></g></svg>',
|
||||
freshTomato:
|
||||
'<svg id="svg3390" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="18" viewBox="0 0 138.75 141.25" width="18" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata id="metadata3396"><rdf:RDF><cc:Work rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/><dc:title/></cc:Work></rdf:RDF></metadata><g id="layer1" fill="#f93208"><path id="path3412" d="m20.154 40.829c-28.149 27.622-13.657 61.011-5.734 71.931 35.254 41.954 92.792 25.339 111.89-5.9071 4.7608-8.2027 22.554-53.467-23.976-78.009z"/><path id="path3471" d="m39.613 39.265 4.7778-8.8607 28.406-5.0384 11.119 9.2082z"/></g><g id="layer2"><path id="path3437" d="m39.436 8.5696 8.9682-5.2826 6.7569 15.479c3.7925-6.3226 13.79-16.316 24.939-4.6684-4.7281 1.2636-7.5161 3.8553-7.7397 8.4768 15.145-4.1697 31.343 3.2127 33.539 9.0911-10.951-4.314-27.695 10.377-41.771 2.334 0.009 15.045-12.617 16.636-19.902 17.076 2.077-4.996 5.591-9.994 1.474-14.987-7.618 8.171-13.874 10.668-33.17 4.668 4.876-1.679 14.843-11.39 24.448-11.425-6.775-2.467-12.29-2.087-17.814-1.475 2.917-3.961 12.149-15.197 28.625-8.476z" fill="#02902e"/></g></svg>',
|
||||
rottenTomato:
|
||||
@@ -41,6 +37,7 @@ const STATE = {
|
||||
slideshow: {
|
||||
hasInitialized: false,
|
||||
isTransitioning: false,
|
||||
isPaused: false,
|
||||
currentSlideIndex: 0,
|
||||
focusedSlide: null,
|
||||
containerFocused: false,
|
||||
@@ -156,11 +153,11 @@ const initJellyfinData = (callback) => {
|
||||
const initLoadingScreen = () => {
|
||||
const currentPath = window.location.href.toLowerCase().replace(window.location.origin, "");
|
||||
const isHomePage =
|
||||
currentPath === "/web/#/home.html" ||
|
||||
currentPath === "/web/#/home" ||
|
||||
currentPath === "/web/index.html#/home.html" ||
|
||||
currentPath.includes("/web/#/home.html") ||
|
||||
currentPath.includes("/web/#/home") ||
|
||||
currentPath.includes("/web/index.html#/home.html") ||
|
||||
currentPath === "/web/index.html#/home" ||
|
||||
currentPath === "/web/";
|
||||
currentPath.endsWith("/web/");
|
||||
|
||||
if (!isHomePage) return;
|
||||
|
||||
@@ -170,7 +167,7 @@ const initLoadingScreen = () => {
|
||||
loadingDiv.innerHTML = `
|
||||
<div class="loader-content">
|
||||
<h1>
|
||||
<img src="/web/assets/img/banner-light.png" alt="Server Logo">
|
||||
<div class="splashLogo"></div>
|
||||
</h1>
|
||||
<div class="progress-container">
|
||||
<div class="progress-bar" id="progress-bar"></div>
|
||||
@@ -182,7 +179,7 @@ const initLoadingScreen = () => {
|
||||
document.body.appendChild(loadingDiv);
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
document.querySelector(".bar-loading h1 img").style.opacity = "1";
|
||||
document.querySelector(".bar-loading h1 div").style.opacity = "1";
|
||||
});
|
||||
|
||||
const progressBar = document.getElementById("progress-bar");
|
||||
@@ -252,6 +249,7 @@ const resetSlideshowState = () => {
|
||||
|
||||
STATE.slideshow.hasInitialized = false;
|
||||
STATE.slideshow.isTransitioning = false;
|
||||
STATE.slideshow.isPaused = false;
|
||||
STATE.slideshow.currentSlideIndex = 0;
|
||||
STATE.slideshow.focusedSlide = null;
|
||||
STATE.slideshow.containerFocused = false;
|
||||
@@ -431,7 +429,7 @@ const SlideUtils = {
|
||||
*/
|
||||
parseGenres(genresArray) {
|
||||
if (Array.isArray(genresArray) && genresArray.length > 0) {
|
||||
return genresArray.slice(0, 3).join(" ▫️ "); //🔹
|
||||
return genresArray.slice(0, 3).join(this.createSeparator().outerHTML);
|
||||
}
|
||||
return "No Genre Available";
|
||||
},
|
||||
@@ -742,7 +740,7 @@ const VisibilityObserver = {
|
||||
container.style.display = isVisible ? "block" : "none";
|
||||
|
||||
if (isVisible) {
|
||||
if (STATE.slideshow.slideInterval) {
|
||||
if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) {
|
||||
STATE.slideshow.slideInterval.start();
|
||||
}
|
||||
} else {
|
||||
@@ -812,7 +810,7 @@ const SlideCreator = {
|
||||
const logo = SlideUtils.createElement("img", {
|
||||
className: "logo high-quality",
|
||||
src: `${serverAddress}/Items/${itemId}/Images/Logo?quality=40`,
|
||||
alt: "logo",
|
||||
alt: item.Name,
|
||||
loading: "eager",
|
||||
});
|
||||
|
||||
@@ -857,8 +855,8 @@ const SlideCreator = {
|
||||
|
||||
const genreElement = SlideUtils.createElement("div", {
|
||||
className: "genre",
|
||||
innerHTML: SlideUtils.parseGenres(item.Genres)
|
||||
});
|
||||
genreElement.innerHTML = SlideUtils.parseGenres(item.Genres);
|
||||
|
||||
const buttonContainer = SlideUtils.createElement("div", {
|
||||
className: "button-container",
|
||||
@@ -890,139 +888,81 @@ const SlideCreator = {
|
||||
*/
|
||||
createRatingInfo(item) {
|
||||
const {
|
||||
CommunityRating: rating,
|
||||
CommunityRating: communityRating,
|
||||
CriticRating: criticRating,
|
||||
OfficialRating: age,
|
||||
PremiereDate: date,
|
||||
OfficialRating: ageRating,
|
||||
PremiereDate: premiereDate,
|
||||
RunTimeTicks: runtime,
|
||||
ChildCount: season,
|
||||
ChildCount: seasonCount,
|
||||
} = item;
|
||||
|
||||
const ratingTest = SlideUtils.createElement("div", {
|
||||
className: "rating-value",
|
||||
const miscInfo = SlideUtils.createElement("div", {
|
||||
className: "misc-info",
|
||||
});
|
||||
|
||||
const imdbLogo = SlideUtils.createElement("div", {
|
||||
className: "imdb-logo",
|
||||
innerHTML: CONFIG.IMAGE_SVG.imdbLogo,
|
||||
style: {
|
||||
width: "30px",
|
||||
height: "30px",
|
||||
},
|
||||
});
|
||||
|
||||
ratingTest.appendChild(imdbLogo);
|
||||
|
||||
if (typeof rating === "number") {
|
||||
const ratingSpan = document.createElement("span");
|
||||
ratingSpan.textContent = rating.toFixed(1);
|
||||
ratingSpan.style.marginRight = "5px";
|
||||
ratingSpan.style.marginLeft = "5px";
|
||||
ratingTest.appendChild(ratingSpan);
|
||||
} else {
|
||||
const naSpan = document.createElement("span");
|
||||
naSpan.innerHTML = "N/A";
|
||||
naSpan.style.color = "#fff9";
|
||||
naSpan.style.marginRight = "5px";
|
||||
naSpan.style.marginLeft = "5px";
|
||||
ratingTest.appendChild(naSpan);
|
||||
// Community Rating Section (IMDb)
|
||||
if (typeof communityRating === "number") {
|
||||
const container = SlideUtils.createElement("div", {
|
||||
className: "star-rating-container",
|
||||
innerHTML: `<span class="material-icons community-rating-star star" aria-hidden="true"></span>${communityRating.toFixed(1)}`,
|
||||
});
|
||||
miscInfo.appendChild(container);
|
||||
miscInfo.appendChild(SlideUtils.createSeparator());
|
||||
}
|
||||
|
||||
ratingTest.appendChild(SlideUtils.createSeparator());
|
||||
|
||||
const tomatoRatingDiv = SlideUtils.createElement("div", {
|
||||
className: "tomato-rating",
|
||||
});
|
||||
|
||||
const tomatoLogo = SlideUtils.createElement("div", {
|
||||
className: "tomato-logo",
|
||||
innerHTML: CONFIG.IMAGE_SVG.tomatoLogo,
|
||||
style: {
|
||||
width: "18px",
|
||||
height: "20px",
|
||||
},
|
||||
});
|
||||
|
||||
let valueElement = SlideUtils.createElement("span", {
|
||||
style: {
|
||||
marginLeft: "5px",
|
||||
marginRight: "5px",
|
||||
},
|
||||
});
|
||||
|
||||
// Critic Rating Section (Rotten Tomatoes)
|
||||
if (typeof criticRating === "number") {
|
||||
valueElement.textContent = `${criticRating}% `;
|
||||
} else {
|
||||
valueElement.style.color = "#fff9";
|
||||
valueElement.textContent = "N/A ";
|
||||
const svgIcon = criticRating < 60 ? CONFIG.IMAGE_SVG.rottenTomato : CONFIG.IMAGE_SVG.freshTomato;
|
||||
const container = SlideUtils.createElement("div", {
|
||||
className: "critic-rating",
|
||||
innerHTML: `${svgIcon}${criticRating.toFixed(0)}%`,
|
||||
})
|
||||
miscInfo.appendChild(container);
|
||||
miscInfo.appendChild(SlideUtils.createSeparator());
|
||||
};
|
||||
|
||||
// Year Section
|
||||
if (typeof premiereDate === "string" && !isNaN(new Date(premiereDate))) {
|
||||
const container = SlideUtils.createElement("div", {
|
||||
className: "date",
|
||||
innerHTML: new Date(premiereDate).getFullYear(),
|
||||
});
|
||||
miscInfo.appendChild(container);
|
||||
miscInfo.appendChild(SlideUtils.createSeparator());
|
||||
};
|
||||
|
||||
// Age Rating Section
|
||||
if (typeof ageRating === "string") {
|
||||
const container = SlideUtils.createElement("div", {
|
||||
className: "age-rating mediaInfoOfficialRating",
|
||||
rating: ageRating,
|
||||
ariaLabel: `Content rated ${ageRating}`,
|
||||
title: `Rating: ${ageRating}`,
|
||||
innerHTML: ageRating,
|
||||
});
|
||||
miscInfo.appendChild(container);
|
||||
miscInfo.appendChild(SlideUtils.createSeparator());
|
||||
};
|
||||
|
||||
// Runtime / Seasons Section
|
||||
if (seasonCount !== undefined || runtime !== undefined) {
|
||||
const container = SlideUtils.createElement("div", {
|
||||
className: "runTime",
|
||||
});
|
||||
if (seasonCount) {
|
||||
container.innerHTML = `${seasonCount} Season${seasonCount > 1 ? "s" : ""}`;
|
||||
} else {
|
||||
const milliseconds = runtime / 10000;
|
||||
const currentTime = new Date();
|
||||
const endTime = new Date(currentTime.getTime() + milliseconds);
|
||||
const options = { hour: "2-digit", minute: "2-digit", hour12: false };
|
||||
const formattedEndTime = endTime.toLocaleTimeString([], options);
|
||||
container.innerText = `Ends at ${formattedEndTime}`;
|
||||
}
|
||||
miscInfo.appendChild(container);
|
||||
}
|
||||
|
||||
const criticLogo = SlideUtils.createElement("span", {
|
||||
className: "critic-logo",
|
||||
style: {
|
||||
display: "flex",
|
||||
width: "18",
|
||||
height: "20",
|
||||
},
|
||||
});
|
||||
criticLogo.innerHTML =
|
||||
criticRating > 59
|
||||
? CONFIG.IMAGE_SVG.freshTomato
|
||||
: CONFIG.IMAGE_SVG.rottenTomato;
|
||||
|
||||
tomatoRatingDiv.append(tomatoLogo, valueElement, criticLogo);
|
||||
tomatoRatingDiv.appendChild(SlideUtils.createSeparator());
|
||||
|
||||
const ageRatingDiv = SlideUtils.createElement("div", {
|
||||
className: "age-rating",
|
||||
});
|
||||
const ageSpan = document.createElement("span");
|
||||
ageSpan.textContent = age || "N/A";
|
||||
ageRatingDiv.appendChild(ageSpan);
|
||||
|
||||
const premiereDate = SlideUtils.createElement("div", {
|
||||
className: "date",
|
||||
});
|
||||
|
||||
const year = date ? new Date(date).getFullYear() : NaN;
|
||||
if (isNaN(year)) {
|
||||
const naSpan = SlideUtils.createElement(
|
||||
"span",
|
||||
{
|
||||
style: { color: "#fff9" },
|
||||
},
|
||||
"N/A"
|
||||
);
|
||||
premiereDate.appendChild(naSpan);
|
||||
} else {
|
||||
premiereDate.textContent = year;
|
||||
}
|
||||
|
||||
const runTimeElement = SlideUtils.createElement("div", {
|
||||
className: "runTime",
|
||||
});
|
||||
|
||||
if (season === undefined) {
|
||||
const milliseconds = runtime / 10000;
|
||||
const currentTime = new Date();
|
||||
const endTime = new Date(currentTime.getTime() + milliseconds);
|
||||
const options = { hour: "2-digit", minute: "2-digit", hour12: false };
|
||||
const formattedEndTime = endTime.toLocaleTimeString([], options);
|
||||
runTimeElement.textContent = `Ends at ${formattedEndTime}`;
|
||||
} else {
|
||||
runTimeElement.textContent = `${season} Season${season > 1 ? "s" : ""}`;
|
||||
}
|
||||
|
||||
ratingTest.append(
|
||||
tomatoRatingDiv,
|
||||
premiereDate,
|
||||
SlideUtils.createSeparator(),
|
||||
ageRatingDiv,
|
||||
SlideUtils.createSeparator(),
|
||||
runTimeElement
|
||||
);
|
||||
|
||||
return ratingTest;
|
||||
return miscInfo;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1076,7 +1016,7 @@ const SlideCreator = {
|
||||
|
||||
createFavoriteButton(item) {
|
||||
const isFavorite = item.UserData && item.UserData.IsFavorite === true;
|
||||
|
||||
|
||||
const button = SlideUtils.createElement("button", {
|
||||
className: `favorite-button ${isFavorite ? "favorited" : ""}`,
|
||||
tabIndex: "0",
|
||||
@@ -1266,7 +1206,7 @@ const SlideshowManager = {
|
||||
this.preloadAdjacentSlides(index);
|
||||
this.updateDots();
|
||||
|
||||
if (STATE.slideshow.slideInterval) {
|
||||
if (STATE.slideshow.slideInterval && !STATE.slideshow.isPaused) {
|
||||
STATE.slideshow.slideInterval.restart();
|
||||
}
|
||||
|
||||
@@ -1377,6 +1317,22 @@ const SlideshowManager = {
|
||||
});
|
||||
},
|
||||
|
||||
togglePause() {
|
||||
STATE.slideshow.isPaused = !STATE.slideshow.isPaused;
|
||||
const pauseButton = document.querySelector('.pause-button');
|
||||
if (STATE.slideshow.isPaused) {
|
||||
STATE.slideshow.slideInterval.stop();
|
||||
pauseButton.innerHTML = '<i class="material-icons">play_arrow</i>';
|
||||
pauseButton.setAttribute("aria-label", "Play slideshow");
|
||||
pauseButton.setAttribute("title", "Play slideshow");
|
||||
} else {
|
||||
STATE.slideshow.slideInterval.start();
|
||||
pauseButton.innerHTML = '<i class="material-icons">pause</i>';
|
||||
pauseButton.setAttribute("aria-label", "Pause slideshow");
|
||||
pauseButton.setAttribute("title", "Pause slideshow");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes touch events for swiping
|
||||
*/
|
||||
@@ -1450,6 +1406,11 @@ const SlideshowManager = {
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
case " ": // Space bar
|
||||
this.togglePause();
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
case "Enter":
|
||||
focusElement.click();
|
||||
e.preventDefault();
|
||||
@@ -1491,7 +1452,9 @@ const SlideshowManager = {
|
||||
await this.updateCurrentSlide(0);
|
||||
|
||||
STATE.slideshow.slideInterval = new SlideTimer(() => {
|
||||
this.nextSlide();
|
||||
if (!STATE.slideshow.isPaused) {
|
||||
this.nextSlide();
|
||||
}
|
||||
}, CONFIG.shuffleInterval);
|
||||
} catch (error) {
|
||||
console.error("Error loading slideshow data:", error);
|
||||
@@ -1539,8 +1502,22 @@ const initArrowNavigation = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const pauseButton = SlideUtils.createElement("div", {
|
||||
className: "pause-button",
|
||||
innerHTML: '<i class="material-icons">pause</i>',
|
||||
tabIndex: "0",
|
||||
"aria-label": "Pause slideshow",
|
||||
title: "Pause slideshow",
|
||||
onclick: (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
SlideshowManager.togglePause();
|
||||
}
|
||||
});
|
||||
|
||||
container.appendChild(leftArrow);
|
||||
container.appendChild(rightArrow);
|
||||
container.appendChild(pauseButton);
|
||||
|
||||
const showArrows = () => {
|
||||
leftArrow.style.display = "block";
|
||||
|
||||
Reference in new issue
Block a user