Skip to content

Commit b5e4776

Browse files
authored
Fix background strip logic
This update fixes the ghost image issue that would happen when navigating away from a Group page that contains a backdrops and into a Group page that had a tmdb link but no backdrop. Stash's defaulted background is kept instead of displaying a ghost image from the previous Group.
1 parent 28c8ba4 commit b5e4776

1 file changed

Lines changed: 86 additions & 79 deletions

File tree

plugins/tmdb-backdrop/tmdb_ui.js

Lines changed: 86 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
(function () {
22
'use strict';
33
let apiKey = null;
4+
let currentImageUrl = null;
45

56
async function waitForElement(selector) {
67
return new Promise(resolve => {
@@ -21,9 +22,35 @@
2122
} catch (e) { console.error("TMDB Plugin: Settings failed", e); }
2223
}
2324

25+
const injectBaseStyles = () => {
26+
if (document.getElementById('tmdb-base-style')) return;
27+
const style = document.createElement('style');
28+
style.id = 'tmdb-base-style';
29+
style.innerHTML = `
30+
#group-page::before, #group-page::after {
31+
content: ""; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
32+
z-index: -1; background-size: cover; background-attachment: fixed;
33+
background-position: center; transition: opacity 1.2s ease-in-out;
34+
opacity: 0; pointer-events: none;
35+
}
36+
/* .tmdb-active controls the initial fade-in from Stash background */
37+
.tmdb-active #group-page { background: transparent !important; }
38+
.tmdb-active #group-page .background-image-container { display: none !important; }
39+
.tmdb-active #group-page .detail-header,
40+
.tmdb-active #group-page .filtered-list-toolbar,
41+
.tmdb-active #group-page .card {
42+
background-color: transparent !important; box-shadow: none !important;
43+
}
44+
.tmdb-active #group-page .detail-body nav { border-bottom: none !important; }
45+
`;
46+
document.head.appendChild(style);
47+
};
48+
49+
let activeLayer = 'before'; // Keep track of which layer is currently visible
50+
2451
const updateBackdrop = async (tmdbUrl) => {
2552
if (!apiKey) await getSettings();
26-
if (!apiKey) return;
53+
if (!apiKey || !tmdbUrl) return;
2754

2855
const idMatch = tmdbUrl.match(/(movie|tv|collection)\/(\d+)/);
2956
if (!idMatch) return;
@@ -34,107 +61,87 @@
3461
const data = await response.json();
3562

3663
if (data.backdrops?.length > 0) {
37-
const imageUrl = `https://image.tmdb.org/t/p/original${data.backdrops[Math.floor(Math.random() * data.backdrops.length)].file_path}`;
64+
const randomPath = data.backdrops[Math.floor(Math.random() * data.backdrops.length)].file_path;
65+
const imageUrl = `https://image.tmdb.org/t/p/original${randomPath}`;
3866

39-
// 1. Get or Create the style block
40-
let styleBlock = document.getElementById('tmdb-dynamic-style');
41-
if (!styleBlock) {
42-
styleBlock = document.createElement('style');
43-
styleBlock.id = 'tmdb-dynamic-style';
44-
document.head.appendChild(styleBlock);
45-
}
67+
if (imageUrl === currentImageUrl && document.body.classList.contains('tmdb-active')) return;
4668

47-
// 2. Start Fade Out: Only the background layer
48-
const styleBase = `
49-
#group-page { position: relative; min-height: 100vh; background: transparent !important; }
50-
#group-page::before {
51-
content: "";
52-
position: fixed;
53-
top: 0; left: 0; width: 100%; height: 100%;
54-
z-index: -1;
55-
background-size: cover;
56-
background-attachment: fixed;
57-
background-position: center;
58-
transition: opacity 0.8s ease-in-out;
59-
}
60-
`;
61-
62-
// Set opacity to 0 on the existing background layer
63-
const currentStyle = styleBlock.innerHTML;
64-
styleBlock.innerHTML = styleBase + currentStyle + `#group-page::before { opacity: 0 !important; }`;
69+
const img = new Image();
70+
img.src = imageUrl;
71+
await img.decode();
6572

66-
// 3. Preload and Wait for fade out
67-
await Promise.all([
68-
new Promise(resolve => setTimeout(resolve, 800)),
69-
new Promise(resolve => { const img = new Image(); img.src = imageUrl; img.onload = resolve; })
70-
]);
73+
let dynamicStyle = document.getElementById('tmdb-dynamic-image');
74+
if (!dynamicStyle) {
75+
dynamicStyle = document.createElement('style');
76+
dynamicStyle.id = 'tmdb-dynamic-image';
77+
document.head.appendChild(dynamicStyle);
78+
}
7179

72-
// 4. Update image and Fade In
73-
styleBlock.innerHTML = `
74-
${styleBase}
75-
#group-page::before {
76-
background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${imageUrl}");
77-
opacity: 1 !important;
78-
}
79-
#group-page .background-image-container { display: none !important; }
80-
#group-page .detail-header, #group-page .filtered-list-toolbar, #group-page .card {
81-
background-color: transparent !important;
82-
box-shadow: none !important;
83-
}
84-
#group-page .detail-body nav { border-bottom: none !important; }
85-
`;
80+
injectBaseStyles();
81+
const isAlreadyActive = document.body.classList.contains('tmdb-active');
82+
83+
if (!isAlreadyActive) {
84+
// INITIAL LOAD
85+
dynamicStyle.innerHTML = `
86+
#group-page::before { background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${imageUrl}"); opacity: 1 !important; }
87+
#group-page::after { opacity: 0 !important; }
88+
`;
89+
document.body.classList.add('tmdb-active');
90+
activeLayer = 'before';
91+
} else {
92+
// SUBPAGE NAVIGATION (CROSS-FADE)
93+
const nextLayer = activeLayer === 'before' ? 'after' : 'before';
94+
95+
// We update the styles so the 'next' layer gets the new image and fades in,
96+
// while the 'current' layer fades out but KEEPS its old image during the transition.
97+
dynamicStyle.innerHTML = `
98+
#group-page::${activeLayer} { background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${currentImageUrl}"); opacity: 0 !important; }
99+
#group-page::${nextLayer} { background-image: linear-gradient(rgba(0,0,0,0.6), rgba(0,0,0,0.6)), url("${imageUrl}"); opacity: 1 !important; }
100+
`;
101+
activeLayer = nextLayer;
102+
}
103+
104+
currentImageUrl = imageUrl;
105+
} else {
106+
clearUI();
86107
}
87-
} catch (e) { console.error("TMDB Plugin Error:", e); }
108+
} catch (e) {
109+
console.error("TMDB Plugin Error:", e);
110+
clearUI();
111+
}
88112
};
89113

114+
function clearUI() {
115+
document.body.classList.remove('tmdb-active');
116+
currentImageUrl = null;
117+
const dynamic = document.getElementById('tmdb-dynamic-image');
118+
if (dynamic) dynamic.remove();
119+
}
90120

91121
async function updateDOM() {
92122
const match = window.location.pathname.match(/\/groups\/(\d+)/);
93-
if (!match) return;
94-
95-
const id = match[1];
96-
// Wait for the specific ID shown in your HTML snippet
123+
if (!match) { clearUI(); return; }
124+
const groupId = match[1];
97125
await waitForElement('#group-page');
98-
99126
const gRes = await fetch('/graphql', {
100127
method: 'POST',
101128
headers: { 'Content-Type': 'application/json' },
102-
body: JSON.stringify({
103-
query: `query FindGroup($id: ID!) { findGroup(id: $id) { urls } }`,
104-
variables: { id: id }
105-
})
129+
body: JSON.stringify({ query: `query FindGroup($id: ID!) { findGroup(id: $id) { urls } }`, variables: { id: groupId } })
106130
});
107-
const gData = await gRes.json();
108-
const urls = gData.data?.findGroup?.urls || [];
131+
const gResult = await gRes.json();
132+
const urls = gResult.data?.findGroup?.urls || [];
109133
const tmdbUrl = urls.find(u => u.toLowerCase().includes('themoviedb.org'));
110-
111-
if (tmdbUrl) {
112-
updateBackdrop(tmdbUrl);
113-
} else {
114-
const styleBlock = document.getElementById('tmdb-dynamic-style');
115-
if (styleBlock) styleBlock.remove();
116-
}
134+
if (tmdbUrl) { updateBackdrop(tmdbUrl); } else { clearUI(); }
117135
}
118136

119-
const handlePathChange = () => {
120-
if (window.location.pathname.match(/\/groups\/\d+/)) {
121-
updateDOM();
122-
}
123-
};
124-
125-
// MutationObserver to catch Stash's internal navigation
126137
const observeUrlChange = () => {
127138
let oldHref = document.location.href;
128-
const body = document.querySelector("body");
129139
const observer = new MutationObserver(() => {
130-
if (oldHref !== document.location.href) {
131-
oldHref = document.location.href;
132-
handlePathChange();
133-
}
140+
if (oldHref !== document.location.href) { oldHref = document.location.href; updateDOM(); }
134141
});
135-
observer.observe(body, { childList: true, subtree: true });
142+
observer.observe(document.body, { childList: true, subtree: true });
136143
};
137144

138-
handlePathChange();
145+
updateDOM();
139146
observeUrlChange();
140147
})();

0 commit comments

Comments
 (0)