From f6fa526598de1d482a97685d1a440443e0d397c3 Mon Sep 17 00:00:00 2001 From: Duncan Tourolle Date: Wed, 17 Jun 2026 22:23:19 +0200 Subject: [PATCH] Fix dropdown --- Jellyfin.Plugin.JellyLMS/Web/remote-button.js | 144 +++++++++--------- 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/Jellyfin.Plugin.JellyLMS/Web/remote-button.js b/Jellyfin.Plugin.JellyLMS/Web/remote-button.js index 6981e15..f4f08bc 100644 --- a/Jellyfin.Plugin.JellyLMS/Web/remote-button.js +++ b/Jellyfin.Plugin.JellyLMS/Web/remote-button.js @@ -36,12 +36,13 @@ // ---------- panel ---------- function createPanel(anchorBtn) { + var rect = anchorBtn.getBoundingClientRect(); var p = document.createElement('div'); p.id = 'jellylms-panel'; p.style.cssText = [ 'position:fixed', - 'top:' + (anchorBtn.getBoundingClientRect().bottom + 4) + 'px', - 'right:' + (window.innerWidth - anchorBtn.getBoundingClientRect().right) + 'px', + 'top:' + (rect.bottom + 4) + 'px', + 'right:' + (window.innerWidth - rect.right) + 'px', 'width:300px', 'max-height:70vh', 'overflow-y:auto', @@ -69,29 +70,26 @@ p.innerHTML = '
' + msg + '
'; } - function renderPlayers(p, players, groups) { + function renderPlayers(p, players) { p.innerHTML = ''; - var masterMacs = {}; - groups.forEach(function (g) { - masterMacs[g.masterMac] = g; - (g.slaveMacs || []).forEach(function (s) { masterMacs[s] = g; }); - }); - // ---- Players ---- - var hdr = el('div', 'font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#888;margin-bottom:6px', 'Players'); - p.appendChild(hdr); + p.appendChild(el('div', + 'font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#888;margin-bottom:8px', + 'Players')); players.forEach(function (player) { - var row = el('div', 'display:flex;align-items:center;gap:8px;margin-bottom:10px'); + // name row: status dot + name + power button + var row = el('div', 'display:flex;align-items:center;gap:8px;margin-bottom:6px'); - var dot = el('span', + row.appendChild(el('span', 'width:8px;height:8px;border-radius:50%;flex-shrink:0;background:' + - (player.isConnected ? '#4caf50' : '#555')); - row.appendChild(dot); + (player.isConnected ? '#4caf50' : '#555'))); - var name = el('span', 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap', player.name); - row.appendChild(name); + var label = player.name || player.macAddress; + row.appendChild(el('span', + 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap', + label)); var pwrBtn = el('button', 'background:none;border:1px solid #555;border-radius:3px;color:#ddd;' + @@ -99,30 +97,39 @@ player.isPoweredOn ? 'Off' : 'On'); pwrBtn.title = player.isPoweredOn ? 'Power off' : 'Power on'; pwrBtn.addEventListener('click', function () { - var endpoint = player.isPoweredOn ? '/Players/' + player.mac + '/PowerOff' : '/Players/' + player.mac + '/PowerOn'; + var endpoint = player.isPoweredOn + ? '/Players/' + player.macAddress + '/PowerOff' + : '/Players/' + player.macAddress + '/PowerOn'; api(endpoint, { method: 'POST' }).then(function () { refresh(p); }); }); row.appendChild(pwrBtn); - p.appendChild(row); - if (player.isConnected) { - var volRow = el('div', 'display:flex;align-items:center;gap:8px;margin-bottom:10px;padding-left:16px'); - var volIcon = el('span', 'color:#888;font-size:16px;font-family:\'Material Icons\';line-height:1', 'volume_up'); - volRow.appendChild(volIcon); + // volume row — show when powered on + if (player.isPoweredOn) { + var volRow = el('div', + 'display:flex;align-items:center;gap:8px;margin-bottom:8px;padding-left:16px'); - var slider = el('input'); + volRow.appendChild(el('span', + 'color:#888;font-size:16px;font-family:"Material Icons";line-height:1', + 'volume_up')); + + var slider = document.createElement('input'); slider.type = 'range'; slider.min = 0; slider.max = 100; - slider.value = player.volume || 0; + slider.value = player.volume; slider.style.cssText = 'flex:1;accent-color:#00a4dc'; slider.addEventListener('change', function () { - api('/Players/' + player.mac + '/Volume', { method: 'POST', body: { volume: parseInt(slider.value, 10) } }); + api('/Players/' + player.macAddress + '/Volume', { + method: 'POST', + body: { volume: parseInt(slider.value, 10) } + }); }); volRow.appendChild(slider); - var volVal = el('span', 'color:#888;font-size:12px;width:28px;text-align:right', player.volume + '%'); + var volVal = el('span', 'color:#888;font-size:12px;width:28px;text-align:right', + player.volume + '%'); slider.addEventListener('input', function () { volVal.textContent = slider.value + '%'; }); volRow.appendChild(volVal); @@ -131,33 +138,44 @@ }); // ---- Sync Groups ---- - if (groups.length > 0) { - var sep = el('div', 'border-top:1px solid #333;margin:8px 0'); - p.appendChild(sep); + var synced = {}; + players.forEach(function (pl) { + if (pl.syncMaster) { synced[pl.macAddress] = true; } + if (pl.syncSlaves && pl.syncSlaves.length) { + synced[pl.macAddress] = true; + pl.syncSlaves.forEach(function (m) { synced[m] = true; }); + } + }); - var ghdr = el('div', 'font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#888;margin-bottom:6px', 'Sync Groups'); - p.appendChild(ghdr); + var masters = players.filter(function (pl) { + return pl.syncSlaves && pl.syncSlaves.length > 0; + }); - groups.forEach(function (g) { - var master = players.find(function (pl) { return pl.mac === g.masterMac; }); - var slaves = (g.slaveMacs || []).map(function (m) { - return players.find(function (pl) { return pl.mac === m; }); - }).filter(Boolean); + if (masters.length > 0) { + p.appendChild(el('div', 'border-top:1px solid #333;margin:8px 0')); + p.appendChild(el('div', + 'font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#888;margin-bottom:8px', + 'Sync Groups')); - var names = [master ? master.name : g.masterMac] - .concat(slaves.map(function (s) { return s.name; })) - .join(' + '); + masters.forEach(function (master) { + var slaveNames = master.syncSlaves.map(function (mac) { + var found = players.find(function (pl) { return pl.macAddress === mac; }); + return found ? (found.name || mac) : mac; + }); + var label = (master.name || master.macAddress) + ' + ' + slaveNames.join(', '); var grow = el('div', 'display:flex;align-items:center;gap:8px;margin-bottom:8px'); - var glabel = el('span', 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px', names); - grow.appendChild(glabel); + grow.appendChild(el('span', + 'flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px', + label)); var dissolveBtn = el('button', 'background:none;border:1px solid #555;border-radius:3px;color:#f88;' + 'padding:2px 7px;cursor:pointer;font-size:12px;flex-shrink:0', 'Unsync'); dissolveBtn.addEventListener('click', function () { - api('/SyncGroups/' + g.masterMac, { method: 'DELETE' }).then(function () { refresh(p); }); + api('/SyncGroups/' + master.macAddress, { method: 'DELETE' }) + .then(function () { refresh(p); }); }); grow.appendChild(dissolveBtn); p.appendChild(grow); @@ -165,16 +183,13 @@ } // ---- Create Sync Group ---- - var unsynced = players.filter(function (pl) { - return !masterMacs[pl.mac] && pl.isConnected; - }); + var unsynced = players.filter(function (pl) { return !synced[pl.macAddress]; }); if (unsynced.length >= 2) { - var sep2 = el('div', 'border-top:1px solid #333;margin:8px 0'); - p.appendChild(sep2); - - var shdr = el('div', 'font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#888;margin-bottom:6px', 'Create Sync Group'); - p.appendChild(shdr); + p.appendChild(el('div', 'border-top:1px solid #333;margin:8px 0')); + p.appendChild(el('div', + 'font-size:11px;text-transform:uppercase;letter-spacing:.08em;color:#888;margin-bottom:8px', + 'Create Sync Group')); var checkboxes = []; unsynced.forEach(function (pl) { @@ -182,10 +197,10 @@ var cb = document.createElement('input'); cb.type = 'checkbox'; cb.style.accentColor = '#00a4dc'; - cb.dataset.mac = pl.mac; + cb.dataset.mac = pl.macAddress; checkboxes.push(cb); crow.appendChild(cb); - crow.appendChild(el('span', 'flex:1', pl.name)); + crow.appendChild(el('span', 'flex:1', pl.name || pl.macAddress)); p.appendChild(crow); }); @@ -208,14 +223,10 @@ function refresh(p) { p.innerHTML = '
Loading…
'; - Promise.all([ - api('/Players').then(function (r) { return r.json(); }), - api('/SyncGroups').then(function (r) { return r.json(); }) - ]).then(function (results) { - renderPlayers(p, results[0], results[1]); - }).catch(function () { - renderError(p, 'Failed to load players.'); - }); + api('/Players') + .then(function (r) { return r.json(); }) + .then(function (players) { renderPlayers(p, players); }) + .catch(function () { renderError(p, 'Failed to load players.'); }); } function togglePanel(btn) { @@ -227,7 +238,7 @@ panel = createPanel(btn); document.body.appendChild(panel); - refresh(panel, btn); + refresh(panel); } function closePanel() { @@ -279,18 +290,13 @@ tryInject(); var observer = new MutationObserver(function () { - // If our panel was removed by SPA navigation, clean up the reference - if (panel && !document.body.contains(panel)) { - panel = null; - } + if (panel && !document.body.contains(panel)) { panel = null; } tryInject(); }); observer.observe(document.body, { childList: true, subtree: true }); document.addEventListener('click', function (e) { - if (panel && !panel.contains(e.target)) { - closePanel(); - } + if (panel && !panel.contains(e.target)) { closePanel(); } }); }); }