Refactor plugin UI components for Jellyfin 10.11+ compatibility
- Updated item detail page integration to target `.mainDetailButtons`. - Replaced rating dropdown with an icon button and custom selection dialog. - Integrated "Recommend Film" and "Add Movie" as text buttons in Library and Home views. - Aligned UI styles with Jellyfin's native `emby-button` patterns and ElegantFin theme. - Improved URL parameter parsing and added throttling to UI injection logic. Co-authored-by: devitq <118541411+devitq@users.noreply.github.com>
This commit is contained in:
co-authored by
devitq
parent
841ce8534f
commit
fe4b40c3dd
@@ -13,66 +13,142 @@
|
|||||||
|
|
||||||
const showMsg = getAlert();
|
const showMsg = getAlert();
|
||||||
|
|
||||||
|
function createTextButton(text, className, onClick) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.is = 'emby-button';
|
||||||
|
btn.className = `emby-button raised ${className}`;
|
||||||
|
btn.style.margin = '0.5em';
|
||||||
|
btn.style.padding = '0.4em 1em';
|
||||||
|
btn.innerHTML = `<span>${text}</span>`;
|
||||||
|
btn.onclick = onClick;
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createIconButton(icon, title, className, onClick) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.is = 'emby-button';
|
||||||
|
btn.className = `button-flat detailButton emby-button ${className}`;
|
||||||
|
btn.title = title;
|
||||||
|
btn.innerHTML = `
|
||||||
|
<div class="detailButton-content">
|
||||||
|
<span class="material-icons detailButton-icon ${icon}" aria-hidden="true"></span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
btn.onclick = onClick;
|
||||||
|
return btn;
|
||||||
|
}
|
||||||
|
|
||||||
function injectUI() {
|
function injectUI() {
|
||||||
const headerButtons = document.querySelector('.headerViewButtons, .view-library .content-primary, .home-section .sectionTitleContainer');
|
// 1. Item Detail Page - Add icon button for rating
|
||||||
|
const detailButtons = document.querySelector('.mainDetailButtons');
|
||||||
if (headerButtons) {
|
if (detailButtons && !document.querySelector('.btnMovieNightRate')) {
|
||||||
if (!document.querySelector('.btnMovieNightRecommend')) {
|
const itemId = getItemIdFromUrl();
|
||||||
const btn = document.createElement('button');
|
if (itemId) {
|
||||||
btn.className = 'emby-button raised btnMovieNightRecommend';
|
const rateBtn = createIconButton('star_rate', 'Rate on MovieNight', 'btnMovieNightRate', (e) => {
|
||||||
btn.innerHTML = '<span>Recommend Film</span>';
|
e.preventDefault();
|
||||||
btn.style.marginLeft = '1em';
|
e.stopPropagation();
|
||||||
btn.onclick = showRecommendation;
|
showRatingDialog(itemId);
|
||||||
headerButtons.appendChild(btn);
|
});
|
||||||
}
|
const moreBtn = detailButtons.querySelector('.btnMoreCommands');
|
||||||
|
if (moreBtn) {
|
||||||
if (!document.querySelector('.btnMovieNightAddMovie')) {
|
detailButtons.insertBefore(rateBtn, moreBtn);
|
||||||
const btn = document.createElement('button');
|
} else {
|
||||||
btn.className = 'emby-button raised btnMovieNightAddMovie';
|
detailButtons.appendChild(rateBtn);
|
||||||
btn.innerHTML = '<span>Add Movie (STRM)</span>';
|
}
|
||||||
btn.style.marginLeft = '1em';
|
|
||||||
btn.onclick = promptAddMovie;
|
|
||||||
headerButtons.appendChild(btn);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const detailButtons = document.querySelector('.itemDetailButtons, .itemDetailsButtons');
|
// 2. Library Pages - Add text buttons to toolbar
|
||||||
if (detailButtons && !document.querySelector('.movieNightRatingContainer')) {
|
const toolBar = document.querySelector('.libraryPage:not(.itemDetailPage) .flex.align-items-center.justify-content-center.focuscontainer-x');
|
||||||
const itemId = getItemIdFromUrl();
|
if (toolBar && !document.querySelector('.btnMovieNightRecommend')) {
|
||||||
if (itemId) {
|
toolBar.appendChild(createTextButton('Recommend Film', 'btnMovieNightRecommend', (e) => {
|
||||||
const container = document.createElement('div');
|
e.preventDefault();
|
||||||
container.className = 'movieNightRatingContainer';
|
showRecommendation();
|
||||||
container.style.display = 'inline-flex';
|
}));
|
||||||
container.style.alignItems = 'center';
|
toolBar.appendChild(createTextButton('Add Movie (STRM)', 'btnMovieNightAddMovie', (e) => {
|
||||||
container.style.marginLeft = '1em';
|
e.preventDefault();
|
||||||
|
promptAddMovie();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
const label = document.createElement('span');
|
// 3. Home Page - Prepend a MovieNight section
|
||||||
label.innerText = 'MovieNight: ';
|
const homeSections = document.querySelector('.sections.homeSectionsContainer');
|
||||||
label.style.marginRight = '0.5em';
|
if (homeSections && !document.querySelector('.movieNightHomeButtons')) {
|
||||||
container.appendChild(label);
|
const section = document.createElement('div');
|
||||||
|
section.className = 'verticalSection movieNightHomeButtons';
|
||||||
const select = document.createElement('select');
|
section.style.padding = '0 var(--sidePadding)';
|
||||||
select.className = 'emby-select';
|
section.innerHTML = '<h2 class="sectionTitle">MovieNight</h2><div class="movieNightBtnContainer" style="display:flex; flex-wrap:wrap;"></div>';
|
||||||
select.style.padding = '0.2em';
|
const btnContainer = section.querySelector('.movieNightBtnContainer');
|
||||||
for (let i = 0; i <= 10; i++) {
|
btnContainer.appendChild(createTextButton('Recommend Film', 'btnMovieNightRecommend', showRecommendation));
|
||||||
const opt = document.createElement('option');
|
btnContainer.appendChild(createTextButton('Add Movie (STRM)', 'btnMovieNightAddMovie', promptAddMovie));
|
||||||
opt.value = i;
|
homeSections.insertBefore(section, homeSections.firstChild);
|
||||||
opt.innerText = i === 0 ? 'Rate...' : i;
|
|
||||||
select.appendChild(opt);
|
|
||||||
}
|
|
||||||
select.onchange = (e) => submitRating(itemId, e.target.value);
|
|
||||||
container.appendChild(select);
|
|
||||||
|
|
||||||
detailButtons.appendChild(container);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItemIdFromUrl() {
|
function getItemIdFromUrl() {
|
||||||
const params = new URLSearchParams(window.location.search);
|
const queryString = window.location.hash.includes('?') ? window.location.hash.split('?')[1] : window.location.search;
|
||||||
|
const params = new URLSearchParams(queryString);
|
||||||
return params.get('id') || params.get('itemId');
|
return params.get('id') || params.get('itemId');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function showRatingDialog(itemId) {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.className = 'dialogBackdrop dialogBackdropOpened';
|
||||||
|
overlay.style.zIndex = '99998';
|
||||||
|
overlay.style.backgroundColor = 'rgba(0,0,0,0.5)';
|
||||||
|
overlay.style.position = 'fixed';
|
||||||
|
overlay.style.top = '0';
|
||||||
|
overlay.style.left = '0';
|
||||||
|
overlay.style.right = '0';
|
||||||
|
overlay.style.bottom = '0';
|
||||||
|
|
||||||
|
const dialog = document.createElement('div');
|
||||||
|
dialog.className = 'dialog';
|
||||||
|
dialog.style.position = 'fixed';
|
||||||
|
dialog.style.top = '50%';
|
||||||
|
dialog.style.left = '50%';
|
||||||
|
dialog.style.transform = 'translate(-50%, -50%)';
|
||||||
|
dialog.style.zIndex = '99999';
|
||||||
|
dialog.style.padding = '2em';
|
||||||
|
dialog.style.minWidth = '250px';
|
||||||
|
dialog.style.backgroundColor = '#222';
|
||||||
|
dialog.style.borderRadius = '1em';
|
||||||
|
dialog.style.color = 'white';
|
||||||
|
|
||||||
|
dialog.innerHTML = `
|
||||||
|
<h2 style="margin-top:0; text-align:center;">Rate on MovieNight</h2>
|
||||||
|
<div class="rating-grid" style="display:grid; grid-template-columns:repeat(5, 1fr); gap:0.5em; margin:1.5em 0;"></div>
|
||||||
|
<button is="emby-button" class="emby-button button-flat btnCancel" style="width:100%; color: white;">Cancel</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const grid = dialog.querySelector('.rating-grid');
|
||||||
|
for (let i = 1; i <= 10; i++) {
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.type = 'button';
|
||||||
|
btn.is = 'emby-button';
|
||||||
|
btn.className = 'emby-button raised';
|
||||||
|
btn.innerText = i;
|
||||||
|
btn.style.padding = '0.5em';
|
||||||
|
btn.onclick = async () => {
|
||||||
|
cleanup();
|
||||||
|
await submitRating(itemId, i);
|
||||||
|
};
|
||||||
|
grid.appendChild(btn);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanup = () => {
|
||||||
|
if (overlay.parentNode) document.body.removeChild(overlay);
|
||||||
|
};
|
||||||
|
|
||||||
|
dialog.querySelector('.btnCancel').onclick = cleanup;
|
||||||
|
overlay.onclick = (e) => { if (e.target === overlay) cleanup(); };
|
||||||
|
|
||||||
|
overlay.appendChild(dialog);
|
||||||
|
document.body.appendChild(overlay);
|
||||||
|
}
|
||||||
|
|
||||||
async function showRecommendation() {
|
async function showRecommendation() {
|
||||||
const userId = ApiClient.getCurrentUserId();
|
const userId = ApiClient.getCurrentUserId();
|
||||||
try {
|
try {
|
||||||
@@ -114,7 +190,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitRating(itemId, score) {
|
async function submitRating(itemId, score) {
|
||||||
if (score === "0") return;
|
|
||||||
const userId = ApiClient.getCurrentUserId();
|
const userId = ApiClient.getCurrentUserId();
|
||||||
try {
|
try {
|
||||||
await ApiClient.ajax({
|
await ApiClient.ajax({
|
||||||
@@ -130,7 +205,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const observer = new MutationObserver(injectUI);
|
let timeout;
|
||||||
|
const throttledInject = () => {
|
||||||
|
if (timeout) return;
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
injectUI();
|
||||||
|
timeout = null;
|
||||||
|
}, 100);
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new MutationObserver(throttledInject);
|
||||||
observer.observe(document.body, { childList: true, subtree: true });
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
|
|
||||||
injectUI();
|
injectUI();
|
||||||
|
|||||||
Reference in New Issue
Block a user