+
+ // Detect which server is active
+ let serverType = 'none';
+ let libraries = [];
+ let users = [];
+ const currentLib = profileData || {};
+
+ try {
+ // Try each server type to find the active one
+ const plexRes = await fetch('/api/plex/music-libraries');
+ if (plexRes.ok) {
+ const plexData = await plexRes.json();
+ if (plexData.libraries && plexData.libraries.length > 0) {
+ serverType = 'plex';
+ libraries = plexData.libraries;
+ }
+ }
+ } catch (e) {}
+
+ if (serverType === 'none') {
+ try {
+ const jellyRes = await fetch('/api/jellyfin/music-libraries');
+ if (jellyRes.ok) {
+ const jellyData = await jellyRes.json();
+ if (jellyData.libraries && jellyData.libraries.length > 0) {
+ serverType = 'jellyfin';
+ libraries = jellyData.libraries;
+ users = jellyData.users || [];
+ }
+ }
+ } catch (e) {}
+ }
+
+ if (serverType === 'none') {
+ section.innerHTML = `
+
-
-
+ `;
+ } else if (serverType === 'plex') {
+ const selectedLib = currentLib.plex_library_id || '';
+ const optionsHtml = libraries.map(lib => {
+ const name = lib.name || lib.title || lib;
+ const val = typeof lib === 'string' ? lib : (lib.name || lib.title);
+ return `
`;
+ }).join('');
+
+ section.innerHTML = `
+
+
+
Choose which Plex music library your playlists sync to.
+
+
+
+
+
+
+
-
-
-
- ${hasAny ? '
' : ''}
+ `;
+ } else if (serverType === 'jellyfin') {
+ const selectedUser = currentLib.jellyfin_user_id || '';
+ const selectedLib = currentLib.jellyfin_library_id || '';
+
+ const userOpts = users.map(u => {
+ const uid = u.id || u.Id;
+ const uname = u.name || u.Name;
+ return `
`;
+ }).join('');
+
+ const libOpts = libraries.map(lib => {
+ const lid = lib.key || lib.id || lib.Id;
+ const lname = lib.name || lib.Name || lib.title;
+ return `
`;
+ }).join('');
+
+ section.innerHTML = `
+
+
+
Choose which Jellyfin user and library your playlists sync to.
+ ${users.length ? `
` : ''}
+
+
+
+
+
+
+
-
- `;
+ `;
+ }
const existing = document.getElementById('ps-server-library-section');
if (existing) existing.replaceWith(section);
- else body.appendChild(section);
+ else container.appendChild(section);
}
async function savePersonalServerLibrary() {
- const resultEl = document.getElementById('ps-server-result');
- try {
- // Save each server type that has a value
- const plex = document.getElementById('ps-plex-library-id')?.value?.trim();
- const jellyfinUser = document.getElementById('ps-jellyfin-user-id')?.value?.trim();
- const jellyfinLib = document.getElementById('ps-jellyfin-library-id')?.value?.trim();
- const navidrome = document.getElementById('ps-navidrome-library-id')?.value?.trim();
-
- const saves = [];
- if (plex !== undefined) saves.push(fetch('/api/profiles/me/server-library', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ server_type: 'plex', library_id: plex || null })
- }));
- if (jellyfinUser !== undefined || jellyfinLib !== undefined) saves.push(fetch('/api/profiles/me/server-library', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ server_type: 'jellyfin', user_id: jellyfinUser || null, library_id: jellyfinLib || null })
- }));
- if (navidrome !== undefined) saves.push(fetch('/api/profiles/me/server-library', {
- method: 'POST', headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ server_type: 'navidrome', library_id: navidrome || null })
- }));
+ try {
+ const plexSelect = document.getElementById('ps-plex-library-select');
+ const jellyUserSelect = document.getElementById('ps-jellyfin-user-select');
+ const jellyLibSelect = document.getElementById('ps-jellyfin-library-select');
- await Promise.all(saves);
- showToast('Server library settings saved', 'success');
- openPersonalSettings();
- } catch (e) {
- if (resultEl) resultEl.innerHTML = '
Failed to save
';
- }
-}
+ if (plexSelect) {
+ await fetch('/api/profiles/me/server-library', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ server_type: 'plex', library_id: plexSelect.value || null })
+ });
+ }
+ if (jellyUserSelect || jellyLibSelect) {
+ await fetch('/api/profiles/me/server-library', {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ server_type: 'jellyfin',
+ user_id: jellyUserSelect?.value || null,
+ library_id: jellyLibSelect?.value || null
+ })
+ });
+ }
-async function clearPersonalServerLibrary() {
- try {
- await Promise.all([
- fetch('/api/profiles/me/server-library', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server_type: 'plex', library_id: null }) }),
- fetch('/api/profiles/me/server-library', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server_type: 'jellyfin', user_id: null, library_id: null }) }),
- fetch('/api/profiles/me/server-library', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ server_type: 'navidrome', library_id: null }) }),
- ]);
- showToast('Server library settings reset to default', 'info');
- openPersonalSettings();
+ showToast('Server library settings saved', 'success');
} catch (e) {
- showToast('Error resetting settings', 'error');
+ showToast('Error saving settings', 'error');
}
}
@@ -1510,8 +1615,8 @@ async function disconnectPersonalSpotify() {
}
}
-function renderPersonalSettingsLB(data) {
- const body = document.getElementById('personal-settings-body');
+function renderPersonalSettingsLB(data, container) {
+ const body = container || document.getElementById('personal-settings-body');
const connected = data.connected;
const username = data.username || '';
const baseUrl = data.base_url || '';
diff --git a/webui/static/style.css b/webui/static/style.css
index d4431373..5d983f8a 100644
--- a/webui/static/style.css
+++ b/webui/static/style.css
@@ -34001,10 +34001,13 @@ body.downloads-disabled [onclick*="DownloadMissing"]:not([onclick*="close"]) {
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
width: 90%;
- max-width: 460px;
+ max-width: 500px;
+ max-height: 85vh;
box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
animation: ps-slide-up 0.25s ease;
overflow: hidden;
+ display: flex;
+ flex-direction: column;
}
@keyframes ps-slide-up {
from { opacity: 0; transform: translateY(20px) scale(0.97); }
@@ -34043,16 +34046,57 @@ body.downloads-disabled [onclick*="DownloadMissing"]:not([onclick*="close"]) {
color: rgba(255, 255, 255, 0.9);
}
.personal-settings-body {
- padding: 20px 22px 24px;
+ padding: 0;
+ overflow-y: auto;
+ flex: 1;
}
-/* LB Section within Personal Settings */
+/* Tab bar within personal settings */
+.ps-tabbar {
+ display: flex;
+ gap: 2px;
+ padding: 12px 22px 0;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
+ background: rgba(0, 0, 0, 0.15);
+}
+.ps-tab {
+ padding: 8px 16px 10px;
+ border: none;
+ background: transparent;
+ color: rgba(255, 255, 255, 0.4);
+ font-size: 0.82em;
+ font-weight: 500;
+ font-family: inherit;
+ cursor: pointer;
+ border-bottom: 2px solid transparent;
+ transition: all 0.2s;
+ white-space: nowrap;
+}
+.ps-tab:hover {
+ color: rgba(255, 255, 255, 0.7);
+}
+.ps-tab.active {
+ color: #fff;
+ border-bottom-color: var(--accent-color, #1db954);
+ font-weight: 600;
+}
+.ps-tab-content {
+ display: none;
+ padding: 18px 22px 22px;
+}
+.ps-tab-content.active {
+ display: block;
+}
+
+/* Section cards within Personal Settings */
.ps-section {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 16px 18px;
+ margin-bottom: 12px;
}
+.ps-section:last-child { margin-bottom: 0; }
.ps-section-header {
display: flex;
align-items: center;