Lists every music library on the server, remembers the choice per server, and shows it under the title with a picker. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f14a726f32
commit
e8b46bb3aa
1 file changed
+20
-10
+20
-10
@@ -536,7 +536,7 @@ const jfetch = async (path, opts) => {
|
|||||||
return j;
|
return j;
|
||||||
};
|
};
|
||||||
const px = {
|
const px = {
|
||||||
sec: null,
|
secs: null,
|
||||||
async get(path) {
|
async get(path) {
|
||||||
const r = await fetch('/plex' + path, { headers: { Accept: 'application/json' } });
|
const r = await fetch('/plex' + path, { headers: { Accept: 'application/json' } });
|
||||||
if (r.status === 401) throw Object.assign(new Error('Plex session expired'), { auth: true });
|
if (r.status === 401) throw Object.assign(new Error('Plex session expired'), { auth: true });
|
||||||
@@ -544,13 +544,19 @@ const px = {
|
|||||||
return (await r.json()).MediaContainer || {};
|
return (await r.json()).MediaContainer || {};
|
||||||
},
|
},
|
||||||
art: (thumb, w = 300) => thumb ? `/plex/photo/:/transcode?width=${w}&height=${w}&minSize=1&upscale=1&url=${encodeURIComponent(thumb)}` : 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==',
|
art: (thumb, w = 300) => thumb ? `/plex/photo/:/transcode?width=${w}&height=${w}&minSize=1&upscale=1&url=${encodeURIComponent(thumb)}` : 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==',
|
||||||
async section() {
|
// All music libraries on the server; the chosen one is remembered per server.
|
||||||
if (px.sec) return px.sec;
|
async sections() {
|
||||||
|
if (px.secs) return px.secs;
|
||||||
const d = (await px.get('/library/sections')).Directory || [];
|
const d = (await px.get('/library/sections')).Directory || [];
|
||||||
const m = d.find(x => x.type === 'artist');
|
px.secs = d.filter(x => x.type === 'artist').map(x => ({ key: x.key, title: x.title }));
|
||||||
if (!m) throw new Error('No music library found on this Plex server');
|
if (!px.secs.length) { px.secs = null; throw new Error('No music library found on this Plex server'); }
|
||||||
return (px.sec = m.key);
|
return px.secs;
|
||||||
},
|
},
|
||||||
|
async section() {
|
||||||
|
const secs = await px.sections(), want = store.get('plex_lib', {})[px.serverKey];
|
||||||
|
return (secs.find(x => x.key === want) || secs[0]).key;
|
||||||
|
},
|
||||||
|
serverKey: '',
|
||||||
};
|
};
|
||||||
const fmtDur = ms => { const s = Math.round((ms || 0) / 1000); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
|
const fmtDur = ms => { const s = Math.round((ms || 0) / 1000); return Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0'); };
|
||||||
const mapTrack = (t, mc) => {
|
const mapTrack = (t, mc) => {
|
||||||
@@ -606,7 +612,7 @@ async function plexPickServer() {
|
|||||||
<span class="grow"><b>${esc(s.name)}</b><small>${s.owned ? 'Yours' : 'Shared with you'}</small></span>${icon('chev', 'chev')}</button>`).join('')}</div><div class="err" id="pe"></div><div id="pm"></div></div>`;
|
<span class="grow"><b>${esc(s.name)}</b><small>${s.owned ? 'Yours' : 'Shared with you'}</small></span>${icon('chev', 'chev')}</button>`).join('')}</div><div class="err" id="pe"></div><div id="pm"></div></div>`;
|
||||||
v.querySelectorAll('button.row').forEach(b => b.onclick = async () => {
|
v.querySelectorAll('button.row').forEach(b => b.onclick = async () => {
|
||||||
b.disabled = true; $('#pe').textContent = '';
|
b.disabled = true; $('#pe').textContent = '';
|
||||||
try { await jfetch('/api/plex/server', { method: 'POST', body: JSON.stringify({ id: b.dataset.id }) }); px.sec = null; route(); }
|
try { await jfetch('/api/plex/server', { method: 'POST', body: JSON.stringify({ id: b.dataset.id }) }); px.secs = null; route(); }
|
||||||
catch (e) {
|
catch (e) {
|
||||||
b.disabled = false; $('#pe').textContent = e.message;
|
b.disabled = false; $('#pe').textContent = e.message;
|
||||||
// Offer a hand-typed address (e.g. the server's LAN address) when automatic discovery fails.
|
// Offer a hand-typed address (e.g. the server's LAN address) when automatic discovery fails.
|
||||||
@@ -615,7 +621,7 @@ async function plexPickServer() {
|
|||||||
<div class="bar"><button class="btn">Try this address</button></div></form>`;
|
<div class="bar"><button class="btn">Try this address</button></div></form>`;
|
||||||
$('#pf2').onsubmit = async ev => {
|
$('#pf2').onsubmit = async ev => {
|
||||||
ev.preventDefault(); $('#pe').textContent = '';
|
ev.preventDefault(); $('#pe').textContent = '';
|
||||||
try { await jfetch('/api/plex/server', { method: 'POST', body: JSON.stringify({ id: b.dataset.id, uri: $('#pu').value.trim() }) }); px.sec = null; route(); }
|
try { await jfetch('/api/plex/server', { method: 'POST', body: JSON.stringify({ id: b.dataset.id, uri: $('#pu').value.trim() }) }); px.secs = null; route(); }
|
||||||
catch (e2) { $('#pe').textContent = e2.message; }
|
catch (e2) { $('#pe').textContent = e2.message; }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -624,14 +630,18 @@ async function plexPickServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function musicRoute([kind = 'albums', key], st) {
|
async function musicRoute([kind = 'albums', key], st) {
|
||||||
const v = $('#view'), sec = await px.section();
|
px.serverKey = st.server;
|
||||||
|
const v = $('#view'), sec = await px.section(), secs = await px.sections(), cur = secs.find(x => x.key === sec) || secs[0];
|
||||||
const tabs = ['albums', 'artists', 'playlists', 'recent'];
|
const tabs = ['albums', 'artists', 'playlists', 'recent'];
|
||||||
const seg = `<div class="seg">${tabs.map(t => `<a href="#/music/${t}" class="${t === kind ? 'on' : ''}">${t[0].toUpperCase() + t.slice(1)}</a>`).join('')}</div>`;
|
const seg = `<div class="seg">${tabs.map(t => `<a href="#/music/${t}" class="${t === kind ? 'on' : ''}">${t[0].toUpperCase() + t.slice(1)}</a>`).join('')}</div>`;
|
||||||
const card = (href, thumb, title, sub, round) => `<a class="acard ${round ? 'round' : ''}" href="${href}" data-q="${esc((title + ' ' + sub).toLowerCase())}">
|
const card = (href, thumb, title, sub, round) => `<a class="acard ${round ? 'round' : ''}" href="${href}" data-q="${esc((title + ' ' + sub).toLowerCase())}">
|
||||||
<img class="art" loading="lazy" alt="" src="${px.art(thumb)}"><b>${esc(title)}</b><small>${esc(sub)}</small></a>`;
|
<img class="art" loading="lazy" alt="" src="${px.art(thumb)}"><b>${esc(title)}</b><small>${esc(sub)}</small></a>`;
|
||||||
const listing = (items, html) => {
|
const listing = (items, html) => {
|
||||||
v.innerHTML = `<div class="wrap"><h1>Music</h1><p class="sub">${esc(st.server)}</p>${seg}<input type="search" id="mq" placeholder="Filter" autocomplete="off">
|
v.innerHTML = `<div class="wrap"><h1>Music</h1><p class="sub">${esc(st.server)}${secs.length > 1 ? ` · <a href="#" id="plib" style="color:var(--accent)">${esc(cur.title)} ▾</a>` : ''}</p>${seg}<input type="search" id="mq" placeholder="Filter" autocomplete="off">
|
||||||
<div class="agrid" style="margin-top:14px">${html || '<p class="empty">Nothing here</p>'}</div></div>`;
|
<div class="agrid" style="margin-top:14px">${html || '<p class="empty">Nothing here</p>'}</div></div>`;
|
||||||
|
const pl2 = $('#plib');
|
||||||
|
if (pl2) pl2.onclick = e => { e.preventDefault(); actionSheet('Music library', st.server, secs.map(x => ({ label: x.title + (x.key === cur.key ? ' ✓' : ''), icon: 'music',
|
||||||
|
run: () => { const m = store.get('plex_lib', {}); m[st.server] = x.key; store.set('plex_lib', m); location.hash = '#/music/albums'; route(); } }))); };
|
||||||
$('#mq').oninput = e => { const q = e.target.value.toLowerCase(); v.querySelectorAll('.acard').forEach(c => c.style.display = c.dataset.q.includes(q) ? '' : 'none'); };
|
$('#mq').oninput = e => { const q = e.target.value.toLowerCase(); v.querySelectorAll('.acard').forEach(c => c.style.display = c.dataset.q.includes(q) ? '' : 'none'); };
|
||||||
};
|
};
|
||||||
const big = '&X-Plex-Container-Start=0&X-Plex-Container-Size=3000&sort=titleSort';
|
const big = '&X-Plex-Container-Start=0&X-Plex-Container-Size=3000&sort=titleSort';
|
||||||
|
|||||||
Reference in new issue
Block a user