diff --git a/webui/static/script.js b/webui/static/script.js index 38ed34f3..dc88a497 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -74151,20 +74151,29 @@ async function loadServerPlaylists() { 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 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(); - 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'); - 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 (container) container.innerHTML = '
No synced playlists found. Only server playlists that have been synced via SoulSync are shown here.
'; + if (synced.length === 0 && unsynced.length === 0) { + if (container) container.innerHTML = '
No playlists found on your media server.
'; return; } @@ -74176,11 +74185,13 @@ async function loadServerPlaylists() { }; const sIcon = serverIcons[data.server_type] || serverIcons.plex; - container.innerHTML = `
${filtered.map((pl, i) => { + function _renderPlCard(pl, i, isSynced) { const hue = (i * 37 + 200) % 360; 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 ` -
+
@@ -74194,16 +74205,43 @@ async function loadServerPlaylists() {
${_esc(pl.name)}
${pl.track_count} tracks + ${isSynced ? 'Synced' : ''}
`; - }).join('')}
`; + } + + let html = ''; + + if (synced.length > 0) { + html += `
+
+ 🔗 + Synced Playlists + ${synced.length} +
+
${synced.map((pl, i) => _renderPlCard(pl, i, true)).join('')}
+
`; + } + + if (unsynced.length > 0) { + html += `
+
+ 🎵 + Other Server Playlists + ${unsynced.length} +
+
${unsynced.map((pl, i) => _renderPlCard(pl, i + synced.length, false)).join('')}
+
`; + } + + container.innerHTML = html; } catch (e) { if (container) container.innerHTML = `
Error: ${e.message}
`; diff --git a/webui/static/style.css b/webui/static/style.css index d0d678b0..0ac15924 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -54036,6 +54036,64 @@ tr.tag-diff-same { 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 */ .server-pl-grid { display: grid;