Show all server playlists with synced/unsynced visual separation
Server Playlists tab now displays ALL playlists from the media server, split into two sections: Synced Playlists (with mirrored/history match, full opacity, "Synced" badge, "Open Editor" action) and Other Server Playlists (everything else, dimmed at 70%, "View Tracks" action). Both sections have header with icon, title, and count. Unsynced cards fade to full opacity on hover.
This commit is contained in:
parent
a867bba18f
commit
922b350983
2 changed files with 108 additions and 12 deletions
|
|
@ -74151,20 +74151,29 @@ async function loadServerPlaylists() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only show server playlists that have a matching mirrored playlist or sync history entry
|
// Separate synced vs non-synced playlists
|
||||||
const mirroredNames = new Set(mirroredAll.map(p => p.name.trim().toLowerCase()));
|
const mirroredNames = new Set(mirroredAll.map(p => p.name.trim().toLowerCase()));
|
||||||
const syncedNames = new Set(historyNames.map(n => n.trim().toLowerCase()));
|
const syncedNames = new Set(historyNames.map(n => n.trim().toLowerCase()));
|
||||||
const filtered = data.playlists.filter(pl => {
|
const synced = [];
|
||||||
|
const unsynced = [];
|
||||||
|
for (const pl of data.playlists) {
|
||||||
const key = pl.name.trim().toLowerCase();
|
const key = pl.name.trim().toLowerCase();
|
||||||
return mirroredNames.has(key) || syncedNames.has(key);
|
if (mirroredNames.has(key) || syncedNames.has(key)) {
|
||||||
});
|
pl._synced = true;
|
||||||
|
synced.push(pl);
|
||||||
|
} else {
|
||||||
|
pl._synced = false;
|
||||||
|
unsynced.push(pl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
_serverPlaylists = filtered;
|
_serverPlaylists = [...synced, ...unsynced];
|
||||||
const title = document.getElementById('server-tab-title');
|
const title = document.getElementById('server-tab-title');
|
||||||
if (title) title.textContent = `Server Playlists (${data.server_type ? data.server_type.charAt(0).toUpperCase() + data.server_type.slice(1) : ''})`;
|
const serverName = data.server_type ? data.server_type.charAt(0).toUpperCase() + data.server_type.slice(1) : '';
|
||||||
|
if (title) title.textContent = `Server Playlists (${serverName})`;
|
||||||
|
|
||||||
if (filtered.length === 0) {
|
if (synced.length === 0 && unsynced.length === 0) {
|
||||||
if (container) container.innerHTML = '<div class="playlist-placeholder">No synced playlists found. Only server playlists that have been synced via SoulSync are shown here.</div>';
|
if (container) container.innerHTML = '<div class="playlist-placeholder">No playlists found on your media server.</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -74176,11 +74185,13 @@ async function loadServerPlaylists() {
|
||||||
};
|
};
|
||||||
const sIcon = serverIcons[data.server_type] || serverIcons.plex;
|
const sIcon = serverIcons[data.server_type] || serverIcons.plex;
|
||||||
|
|
||||||
container.innerHTML = `<div class="server-pl-grid">${filtered.map((pl, i) => {
|
function _renderPlCard(pl, i, isSynced) {
|
||||||
const hue = (i * 37 + 200) % 360;
|
const hue = (i * 37 + 200) % 360;
|
||||||
const safeName = _esc(pl.name).replace(/'/g, "\\'");
|
const safeName = _esc(pl.name).replace(/'/g, "\\'");
|
||||||
|
const cardClass = isSynced ? 'server-pl-card' : 'server-pl-card server-pl-unsynced';
|
||||||
|
const action = isSynced ? 'Open Editor' : 'View Tracks';
|
||||||
return `
|
return `
|
||||||
<div class="server-pl-card" onclick="openServerPlaylistEditor('${pl.id}', '${safeName}')" style="animation-delay: ${i * 0.04}s; --card-hue: ${hue}">
|
<div class="${cardClass}" onclick="openServerPlaylistEditor('${pl.id}', '${safeName}')" style="animation-delay: ${i * 0.04}s; --card-hue: ${hue}">
|
||||||
<div class="server-pl-card-glow"></div>
|
<div class="server-pl-card-glow"></div>
|
||||||
<div class="server-pl-card-top">
|
<div class="server-pl-card-top">
|
||||||
<div class="server-pl-card-icon-wrap">
|
<div class="server-pl-card-icon-wrap">
|
||||||
|
|
@ -74194,16 +74205,43 @@ async function loadServerPlaylists() {
|
||||||
<div class="server-pl-card-name">${_esc(pl.name)}</div>
|
<div class="server-pl-card-name">${_esc(pl.name)}</div>
|
||||||
<div class="server-pl-card-meta">
|
<div class="server-pl-card-meta">
|
||||||
<span class="server-pl-track-count">${pl.track_count}</span> tracks
|
<span class="server-pl-track-count">${pl.track_count}</span> tracks
|
||||||
|
${isSynced ? '<span class="server-pl-synced-badge">Synced</span>' : ''}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="server-pl-card-footer">
|
<div class="server-pl-card-footer">
|
||||||
<span class="server-pl-card-action">Open Editor</span>
|
<span class="server-pl-card-action">${action}</span>
|
||||||
<span class="server-pl-card-arrow">
|
<span class="server-pl-card-arrow">
|
||||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('')}</div>`;
|
}
|
||||||
|
|
||||||
|
let html = '';
|
||||||
|
|
||||||
|
if (synced.length > 0) {
|
||||||
|
html += `<div class="server-pl-section">
|
||||||
|
<div class="server-pl-section-header">
|
||||||
|
<span class="server-pl-section-icon">🔗</span>
|
||||||
|
<span class="server-pl-section-title">Synced Playlists</span>
|
||||||
|
<span class="server-pl-section-count">${synced.length}</span>
|
||||||
|
</div>
|
||||||
|
<div class="server-pl-grid">${synced.map((pl, i) => _renderPlCard(pl, i, true)).join('')}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsynced.length > 0) {
|
||||||
|
html += `<div class="server-pl-section server-pl-section-unsynced">
|
||||||
|
<div class="server-pl-section-header">
|
||||||
|
<span class="server-pl-section-icon">🎵</span>
|
||||||
|
<span class="server-pl-section-title">Other Server Playlists</span>
|
||||||
|
<span class="server-pl-section-count">${unsynced.length}</span>
|
||||||
|
</div>
|
||||||
|
<div class="server-pl-grid">${unsynced.map((pl, i) => _renderPlCard(pl, i + synced.length, false)).join('')}</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (container) container.innerHTML = `<div class="playlist-placeholder">Error: ${e.message}</div>`;
|
if (container) container.innerHTML = `<div class="playlist-placeholder">Error: ${e.message}</div>`;
|
||||||
|
|
|
||||||
|
|
@ -54036,6 +54036,64 @@ tr.tag-diff-same {
|
||||||
SERVER PLAYLIST MANAGER — Sync Page Server Tab
|
SERVER PLAYLIST MANAGER — Sync Page Server Tab
|
||||||
================================================================================== */
|
================================================================================== */
|
||||||
|
|
||||||
|
/* Playlist sections */
|
||||||
|
.server-pl-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-pl-section-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-pl-section-icon { font-size: 16px; }
|
||||||
|
|
||||||
|
.server-pl-section-title {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(255, 255, 255, 0.5);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-pl-section-count {
|
||||||
|
font-size: 10px;
|
||||||
|
color: rgba(255, 255, 255, 0.25);
|
||||||
|
padding: 1px 7px;
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unsynced cards — dimmer, different accent */
|
||||||
|
.server-pl-unsynced {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-pl-unsynced:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-pl-unsynced .server-pl-card-glow {
|
||||||
|
background: radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.03), transparent 60%) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Synced badge */
|
||||||
|
.server-pl-synced-badge {
|
||||||
|
font-size: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgb(var(--accent-light-rgb));
|
||||||
|
padding: 1px 6px;
|
||||||
|
background: rgba(var(--accent-rgb), 0.12);
|
||||||
|
border: 1px solid rgba(var(--accent-rgb), 0.2);
|
||||||
|
border-radius: 6px;
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Playlist grid layout */
|
/* Playlist grid layout */
|
||||||
.server-pl-grid {
|
.server-pl-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue