Add collapsible accordion UI to Settings Connections tab
Visual overhaul of the API Configuration section: each service frame is now a collapsible accordion with brand-colored dots, chevron indicators, and smooth expand/collapse animations. Includes an Expand All / Collapse All toggle. No functional changes — all element IDs, save/load logic, and tab switching preserved.
This commit is contained in:
parent
e73c1e69c2
commit
afd5125262
3 changed files with 434 additions and 246 deletions
|
|
@ -3952,7 +3952,7 @@
|
|||
<div class="settings-left-column">
|
||||
<!-- API Configuration -->
|
||||
<div class="settings-group" data-stg="connections">
|
||||
<h3>API Configuration</h3>
|
||||
<h3>API Configuration <button class="stg-accordion-toggle" onclick="toggleAllServiceAccordions(this)">Expand All</button></h3>
|
||||
|
||||
<!-- Metadata Source Selection (first — tells user what they're configuring for) -->
|
||||
<div class="api-service-frame">
|
||||
|
|
@ -3972,8 +3972,13 @@
|
|||
</div>
|
||||
|
||||
<!-- Spotify Settings -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #1DB954;"></span>
|
||||
<h4 class="service-title spotify-title">Spotify</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Client ID:</label>
|
||||
<input type="text" id="spotify-client-id" placeholder="Spotify Client ID">
|
||||
|
|
@ -4007,10 +4012,16 @@
|
|||
Clear Cache & Use Fallback</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- iTunes Settings -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #fc3c44;"></span>
|
||||
<h4 class="service-title itunes-title">iTunes / Apple Music</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Storefront Country:</label>
|
||||
<select id="itunes-country">
|
||||
|
|
@ -4036,10 +4047,16 @@
|
|||
<div class="callback-help">Sets the primary Apple Music storefront. Region-specific albums are auto-searched across other storefronts as fallback.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Deezer OAuth Auth -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #A238FF;"></span>
|
||||
<h4 class="service-title deezer-title">Deezer (Favorites & Playlists)</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>App ID:</label>
|
||||
<input type="text" id="deezer-app-id" placeholder="Deezer App ID">
|
||||
|
|
@ -4064,10 +4081,16 @@
|
|||
Authenticate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Discogs Settings -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title" style="color: #333;">Discogs</h4>
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #e0d4b8;"></span>
|
||||
<h4 class="service-title">Discogs</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Personal Access Token:</label>
|
||||
<input type="password" id="discogs-token"
|
||||
|
|
@ -4080,10 +4103,16 @@
|
|||
<div class="callback-help">Click "Generate new token" — no app registration needed. Provides 60 req/min and cover art in search results.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tidal Playlist/Metadata Auth -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #ff6600;"></span>
|
||||
<h4 class="service-title tidal-title">Tidal (Playlists & Metadata)</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Client ID:</label>
|
||||
<input type="text" id="tidal-client-id" placeholder="Tidal Client ID">
|
||||
|
|
@ -4109,10 +4138,16 @@
|
|||
Authenticate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Qobuz Metadata/Enrichment Auth -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #4285f4;"></span>
|
||||
<h4 class="service-title qobuz-title">Qobuz (Metadata & Enrichment)</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div id="qobuz-connection-logged-in" style="display: none; margin-bottom: 8px;">
|
||||
<span id="qobuz-connection-user-info" class="setting-help-text" style="color: #4caf50;"></span>
|
||||
<button class="auth-button" onclick="logoutQobuz()" style="margin-left: 8px;">
|
||||
|
|
@ -4141,10 +4176,16 @@
|
|||
Connects Qobuz for metadata enrichment (ISRC, labels, copyright). Also used for downloads if Qobuz is your download source.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Last.fm Settings -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #d51007;"></span>
|
||||
<h4 class="service-title lastfm-title">Last.fm</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="lastfm-api-key"
|
||||
|
|
@ -4172,10 +4213,16 @@
|
|||
<span class="setting-help-text" id="lastfm-scrobble-status"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Genius Settings -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #ffff64;"></span>
|
||||
<h4 class="service-title genius-title">Genius</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Client Access Token:</label>
|
||||
<input type="password" id="genius-access-token"
|
||||
|
|
@ -4188,10 +4235,16 @@
|
|||
<div class="callback-help">Generate a "Client Access Token" — no OAuth flow needed.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AcoustID Settings -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #ba55d3;"></span>
|
||||
<h4 class="service-title acoustid-title">AcoustID Verification</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group" style="margin-bottom: 12px;">
|
||||
<label class="checkbox-label"
|
||||
style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||
|
|
@ -4219,10 +4272,16 @@
|
|||
</div>
|
||||
<button class="test-button" onclick="clearQuarantine()" style="margin-top: 10px; background: rgba(255,82,82,0.15); border-color: rgba(255,82,82,0.3); color: #ff5252;">Clear Quarantine</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ListenBrainz Settings -->
|
||||
<div class="api-service-frame">
|
||||
<div class="api-service-frame stg-service">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #eb743b;"></span>
|
||||
<h4 class="service-title listenbrainz-title">ListenBrainz</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>API Base URL:</label>
|
||||
<input type="text" id="listenbrainz-base-url"
|
||||
|
|
@ -4246,10 +4305,16 @@
|
|||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hydrabase P2P Metadata -->
|
||||
<div class="api-service-frame" data-stg="connections">
|
||||
<h4 class="service-title" style="color: #00b4d8;">Hydrabase</h4>
|
||||
<div class="api-service-frame stg-service" data-stg="connections">
|
||||
<div class="stg-service-header" onclick="toggleStgService(this)">
|
||||
<span class="stg-service-dot" style="color: #00b4d8;"></span>
|
||||
<h4 class="service-title">Hydrabase</h4>
|
||||
<span class="stg-service-chevron">›</span>
|
||||
</div>
|
||||
<div class="stg-service-body">
|
||||
<input type="hidden" id="hydrabase-enabled" value="false">
|
||||
<div class="form-group">
|
||||
<label>WebSocket URL:</label>
|
||||
|
|
@ -4273,6 +4338,7 @@
|
|||
<span id="hydrabase-settings-status" style="font-size: 0.82em; color: rgba(255,255,255,0.4); margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Test Connection Buttons -->
|
||||
<div class="api-test-buttons">
|
||||
|
|
|
|||
|
|
@ -5555,6 +5555,12 @@ function toggleStgService(el) {
|
|||
const service = el.closest('.stg-service');
|
||||
if (service) service.classList.toggle('expanded');
|
||||
}
|
||||
function toggleAllServiceAccordions(btn) {
|
||||
const services = document.querySelectorAll('#settings-page .stg-service');
|
||||
const allExpanded = Array.from(services).every(s => s.classList.contains('expanded'));
|
||||
services.forEach(s => s.classList.toggle('expanded', !allExpanded));
|
||||
btn.textContent = allExpanded ? 'Expand All' : 'Collapse All';
|
||||
}
|
||||
|
||||
// ── Hybrid source priority list (drag-and-drop) ──
|
||||
const HYBRID_SOURCES = [
|
||||
|
|
|
|||
|
|
@ -51081,6 +51081,119 @@ tr.tag-diff-same {
|
|||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
/* ── Settings Accordion Services — visual upgrade ── */
|
||||
#settings-page .api-service-frame.stg-service {
|
||||
overflow: hidden;
|
||||
transition: border-color 0.25s, box-shadow 0.25s, transform 0.25s, border-left-color 0.3s;
|
||||
}
|
||||
#settings-page .api-service-frame.stg-service:hover {
|
||||
transform: none;
|
||||
}
|
||||
|
||||
#settings-page .stg-service > .stg-service-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 13px 18px;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease, padding-left 0.25s ease;
|
||||
user-select: none;
|
||||
}
|
||||
#settings-page .stg-service > .stg-service-header:hover {
|
||||
background: rgba(255, 255, 255, 0.035);
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
/* Override service-title styles inside accordion header */
|
||||
#settings-page .stg-service .stg-service-header .service-title {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
border-bottom: none !important;
|
||||
font-size: 0.88em !important;
|
||||
font-weight: 500;
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: none;
|
||||
flex: 1;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
#settings-page .stg-service:hover .stg-service-header .service-title,
|
||||
#settings-page .stg-service.expanded .stg-service-header .service-title {
|
||||
color: rgba(255, 255, 255, 0.95);
|
||||
}
|
||||
|
||||
/* Brand color dot */
|
||||
#settings-page .stg-service-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
background: currentColor;
|
||||
opacity: 0.45;
|
||||
transition: opacity 0.25s, transform 0.25s, box-shadow 0.25s;
|
||||
}
|
||||
#settings-page .stg-service:hover .stg-service-dot {
|
||||
opacity: 0.75;
|
||||
}
|
||||
#settings-page .stg-service.expanded .stg-service-dot {
|
||||
opacity: 1;
|
||||
transform: scale(1.25);
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
/* Chevron */
|
||||
#settings-page .stg-service > .stg-service-header > .stg-service-chevron {
|
||||
color: rgba(255, 255, 255, 0.2);
|
||||
font-size: 1.1em;
|
||||
transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1), color 0.25s;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
line-height: 1;
|
||||
}
|
||||
#settings-page .stg-service.expanded > .stg-service-header > .stg-service-chevron {
|
||||
transform: rotate(90deg);
|
||||
color: var(--accent-color, #1db954);
|
||||
}
|
||||
|
||||
/* Collapsible body */
|
||||
#settings-page .stg-service > .stg-service-body {
|
||||
display: none;
|
||||
padding: 0 0 6px;
|
||||
border-top: 1px solid rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
#settings-page .stg-service.expanded > .stg-service-body {
|
||||
display: block;
|
||||
animation: stgSlideIn 0.25s ease-out;
|
||||
}
|
||||
|
||||
/* Expanded state — accent left border + slightly brighter bg */
|
||||
#settings-page .api-service-frame.stg-service.expanded {
|
||||
border-left: 2px solid rgba(var(--accent-rgb, 29, 185, 84), 0.4);
|
||||
background: rgba(255, 255, 255, 0.025);
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15), inset 0 1px 0 rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
/* Expand All / Collapse All toggle button */
|
||||
#settings-page .stg-accordion-toggle {
|
||||
background: none;
|
||||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
font-size: 0.72em;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
padding: 4px 10px;
|
||||
border-radius: 6px;
|
||||
transition: color 0.2s, background 0.2s, border-color 0.2s;
|
||||
margin-left: auto;
|
||||
letter-spacing: 0.03em;
|
||||
font-family: inherit;
|
||||
}
|
||||
#settings-page .stg-accordion-toggle:hover {
|
||||
color: rgba(255, 255, 255, 0.7);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* ── Accent color picker ── */
|
||||
#settings-page .accent-color-selector {
|
||||
display: flex;
|
||||
|
|
@ -51121,6 +51234,9 @@ tr.tag-diff-same {
|
|||
#settings-page .api-service-frame .form-group { padding: 8px 12px !important; }
|
||||
#settings-page .api-service-frame .callback-info { padding: 2px 12px 10px; }
|
||||
#settings-page .api-service-frame .form-actions { padding: 4px 12px 12px; }
|
||||
/* Accordion headers */
|
||||
#settings-page .stg-service > .stg-service-header { padding: 12px 14px; }
|
||||
#settings-page .stg-service > .stg-service-body { padding: 0 0 8px; }
|
||||
/* Save button */
|
||||
#settings-page .settings-actions { padding: 16px 8px; }
|
||||
#settings-page .settings-actions .save-button { width: 100%; }
|
||||
|
|
|
|||
Loading…
Reference in a new issue