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
558
webui/index.html
558
webui/index.html
|
|
@ -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,305 +3972,371 @@
|
|||
</div>
|
||||
|
||||
<!-- Spotify Settings -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title spotify-title">Spotify</h4>
|
||||
<div class="form-group">
|
||||
<label>Client ID:</label>
|
||||
<input type="text" id="spotify-client-id" placeholder="Spotify Client ID">
|
||||
<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="form-group">
|
||||
<label>Client Secret:</label>
|
||||
<input type="password" id="spotify-client-secret"
|
||||
placeholder="Spotify Client Secret">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Redirect URI:</label>
|
||||
<input type="text" id="spotify-redirect-uri"
|
||||
placeholder="http://127.0.0.1:8888/callback">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-label">Current Redirect URI:</div>
|
||||
<div class="callback-url" id="spotify-callback-display">
|
||||
http://127.0.0.1:8888/callback</div>
|
||||
<div class="callback-help">Add this URL to your Spotify app's 'Redirect URIs' in
|
||||
the Spotify Developer Dashboard</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" onclick="authenticateSpotify()">🔐
|
||||
Authenticate</button>
|
||||
<button class="auth-button disconnect-button" id="spotify-disconnect-btn"
|
||||
onclick="disconnectSpotify()" style="display: none;">🔌
|
||||
Disconnect</button>
|
||||
<button class="auth-button disconnect-button"
|
||||
onclick="clearSpotifyCacheAndFallback()"
|
||||
title="Clear Spotify token cache and switch to your configured fallback metadata source">🗑️
|
||||
Clear Cache & Use Fallback</button>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Client ID:</label>
|
||||
<input type="text" id="spotify-client-id" placeholder="Spotify Client ID">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Client Secret:</label>
|
||||
<input type="password" id="spotify-client-secret"
|
||||
placeholder="Spotify Client Secret">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Redirect URI:</label>
|
||||
<input type="text" id="spotify-redirect-uri"
|
||||
placeholder="http://127.0.0.1:8888/callback">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-label">Current Redirect URI:</div>
|
||||
<div class="callback-url" id="spotify-callback-display">
|
||||
http://127.0.0.1:8888/callback</div>
|
||||
<div class="callback-help">Add this URL to your Spotify app's 'Redirect URIs' in
|
||||
the Spotify Developer Dashboard</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" onclick="authenticateSpotify()">🔐
|
||||
Authenticate</button>
|
||||
<button class="auth-button disconnect-button" id="spotify-disconnect-btn"
|
||||
onclick="disconnectSpotify()" style="display: none;">🔌
|
||||
Disconnect</button>
|
||||
<button class="auth-button disconnect-button"
|
||||
onclick="clearSpotifyCacheAndFallback()"
|
||||
title="Clear Spotify token cache and switch to your configured fallback metadata source">🗑️
|
||||
Clear Cache & Use Fallback</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- iTunes Settings -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title itunes-title">iTunes / Apple Music</h4>
|
||||
<div class="form-group">
|
||||
<label>Storefront Country:</label>
|
||||
<select id="itunes-country">
|
||||
<option value="US">United States (US)</option>
|
||||
<option value="GB">United Kingdom (GB)</option>
|
||||
<option value="CA">Canada (CA)</option>
|
||||
<option value="AU">Australia (AU)</option>
|
||||
<option value="DE">Germany (DE)</option>
|
||||
<option value="FR">France (FR)</option>
|
||||
<option value="JP">Japan (JP)</option>
|
||||
<option value="KR">South Korea (KR)</option>
|
||||
<option value="BR">Brazil (BR)</option>
|
||||
<option value="SE">Sweden (SE)</option>
|
||||
<option value="NL">Netherlands (NL)</option>
|
||||
<option value="IT">Italy (IT)</option>
|
||||
<option value="ES">Spain (ES)</option>
|
||||
<option value="MX">Mexico (MX)</option>
|
||||
<option value="IN">India (IN)</option>
|
||||
<option value="RU">Russia (RU)</option>
|
||||
</select>
|
||||
<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="callback-info">
|
||||
<div class="callback-help">Sets the primary Apple Music storefront. Region-specific albums are auto-searched across other storefronts as fallback.</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Storefront Country:</label>
|
||||
<select id="itunes-country">
|
||||
<option value="US">United States (US)</option>
|
||||
<option value="GB">United Kingdom (GB)</option>
|
||||
<option value="CA">Canada (CA)</option>
|
||||
<option value="AU">Australia (AU)</option>
|
||||
<option value="DE">Germany (DE)</option>
|
||||
<option value="FR">France (FR)</option>
|
||||
<option value="JP">Japan (JP)</option>
|
||||
<option value="KR">South Korea (KR)</option>
|
||||
<option value="BR">Brazil (BR)</option>
|
||||
<option value="SE">Sweden (SE)</option>
|
||||
<option value="NL">Netherlands (NL)</option>
|
||||
<option value="IT">Italy (IT)</option>
|
||||
<option value="ES">Spain (ES)</option>
|
||||
<option value="MX">Mexico (MX)</option>
|
||||
<option value="IN">India (IN)</option>
|
||||
<option value="RU">Russia (RU)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<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">
|
||||
<h4 class="service-title deezer-title">Deezer (Favorites & Playlists)</h4>
|
||||
<div class="form-group">
|
||||
<label>App ID:</label>
|
||||
<input type="text" id="deezer-app-id" placeholder="Deezer App ID">
|
||||
<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="form-group">
|
||||
<label>App Secret:</label>
|
||||
<input type="password" id="deezer-app-secret" placeholder="Deezer App Secret">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Redirect URI:</label>
|
||||
<input type="text" id="deezer-redirect-uri"
|
||||
placeholder="http://127.0.0.1:8008/deezer/callback">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-label">Current Redirect URI:</div>
|
||||
<div class="callback-url" id="deezer-callback-display">
|
||||
http://127.0.0.1:8008/deezer/callback</div>
|
||||
<div class="callback-help">Add this URL to your Deezer app at developers.deezer.com</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" onclick="authenticateDeezer()">🔐
|
||||
Authenticate</button>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>App ID:</label>
|
||||
<input type="text" id="deezer-app-id" placeholder="Deezer App ID">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>App Secret:</label>
|
||||
<input type="password" id="deezer-app-secret" placeholder="Deezer App Secret">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Redirect URI:</label>
|
||||
<input type="text" id="deezer-redirect-uri"
|
||||
placeholder="http://127.0.0.1:8008/deezer/callback">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-label">Current Redirect URI:</div>
|
||||
<div class="callback-url" id="deezer-callback-display">
|
||||
http://127.0.0.1:8008/deezer/callback</div>
|
||||
<div class="callback-help">Add this URL to your Deezer app at developers.deezer.com</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" onclick="authenticateDeezer()">🔐
|
||||
Authenticate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Discogs Settings -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title" style="color: #333;">Discogs</h4>
|
||||
<div class="form-group">
|
||||
<label>Personal Access Token:</label>
|
||||
<input type="password" id="discogs-token"
|
||||
placeholder="Discogs Personal Access Token">
|
||||
<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="callback-info">
|
||||
<div class="callback-help">Get your free token from <a
|
||||
href="https://www.discogs.com/settings/developers" target="_blank"
|
||||
style="color: #ffff64;">Discogs Developer Settings</a></div>
|
||||
<div class="callback-help">Click "Generate new token" — no app registration needed. Provides 60 req/min and cover art in search results.</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Personal Access Token:</label>
|
||||
<input type="password" id="discogs-token"
|
||||
placeholder="Discogs Personal Access Token">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your free token from <a
|
||||
href="https://www.discogs.com/settings/developers" target="_blank"
|
||||
style="color: #ffff64;">Discogs Developer Settings</a></div>
|
||||
<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">
|
||||
<h4 class="service-title tidal-title">Tidal (Playlists & Metadata)</h4>
|
||||
<div class="form-group">
|
||||
<label>Client ID:</label>
|
||||
<input type="text" id="tidal-client-id" placeholder="Tidal Client ID">
|
||||
<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="form-group">
|
||||
<label>Client Secret:</label>
|
||||
<input type="password" id="tidal-client-secret"
|
||||
placeholder="Tidal Client Secret">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Redirect URI:</label>
|
||||
<input type="text" id="tidal-redirect-uri"
|
||||
placeholder="http://127.0.0.1:8889/tidal/callback">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-label">Current Redirect URI:</div>
|
||||
<div class="callback-url" id="tidal-callback-display">
|
||||
http://127.0.0.1:8889/tidal/callback</div>
|
||||
<div class="callback-help">Add this URL to your Tidal app configuration</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" onclick="authenticateTidal()">🔐
|
||||
Authenticate</button>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Client ID:</label>
|
||||
<input type="text" id="tidal-client-id" placeholder="Tidal Client ID">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Client Secret:</label>
|
||||
<input type="password" id="tidal-client-secret"
|
||||
placeholder="Tidal Client Secret">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Redirect URI:</label>
|
||||
<input type="text" id="tidal-redirect-uri"
|
||||
placeholder="http://127.0.0.1:8889/tidal/callback">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-label">Current Redirect URI:</div>
|
||||
<div class="callback-url" id="tidal-callback-display">
|
||||
http://127.0.0.1:8889/tidal/callback</div>
|
||||
<div class="callback-help">Add this URL to your Tidal app configuration</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" onclick="authenticateTidal()">🔐
|
||||
Authenticate</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Qobuz Metadata/Enrichment Auth -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title qobuz-title">Qobuz (Metadata & Enrichment)</h4>
|
||||
<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;">
|
||||
Disconnect
|
||||
</button>
|
||||
<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 id="qobuz-connection-form">
|
||||
<div class="form-group">
|
||||
<label>Email:</label>
|
||||
<input type="email" id="qobuz-connection-email" class="form-input"
|
||||
placeholder="Qobuz email" autocomplete="email">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password:</label>
|
||||
<input type="password" id="qobuz-connection-password" class="form-input"
|
||||
placeholder="Qobuz password" autocomplete="current-password">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" id="qobuz-connection-login-btn" onclick="loginQobuzFromConnections()">
|
||||
Connect Qobuz
|
||||
<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;">
|
||||
Disconnect
|
||||
</button>
|
||||
<span id="qobuz-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-help-text" style="margin-top: 6px;">
|
||||
Connects Qobuz for metadata enrichment (ISRC, labels, copyright). Also used for downloads if Qobuz is your download source.
|
||||
<div id="qobuz-connection-form">
|
||||
<div class="form-group">
|
||||
<label>Email:</label>
|
||||
<input type="email" id="qobuz-connection-email" class="form-input"
|
||||
placeholder="Qobuz email" autocomplete="email">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Password:</label>
|
||||
<input type="password" id="qobuz-connection-password" class="form-input"
|
||||
placeholder="Qobuz password" autocomplete="current-password">
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="auth-button" id="qobuz-connection-login-btn" onclick="loginQobuzFromConnections()">
|
||||
Connect Qobuz
|
||||
</button>
|
||||
<span id="qobuz-connection-status" class="setting-help-text" style="margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-help-text" style="margin-top: 6px;">
|
||||
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">
|
||||
<h4 class="service-title lastfm-title">Last.fm</h4>
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="lastfm-api-key"
|
||||
placeholder="Last.fm API Key">
|
||||
<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="form-group">
|
||||
<label>API Secret:</label>
|
||||
<input type="password" id="lastfm-api-secret"
|
||||
placeholder="Last.fm API Secret (required for scrobbling)">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your API key and secret from <a
|
||||
href="https://www.last.fm/api/account/create" target="_blank"
|
||||
style="color: #d51007;">Last.fm API Account</a></div>
|
||||
<div class="callback-help">API key: used for metadata enrichment. API secret: required for scrobbling.</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 12px;">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="lastfm-scrobble-enabled">
|
||||
Scrobble plays to Last.fm
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions" id="lastfm-scrobble-actions">
|
||||
<button class="test-button" onclick="authorizeLastfmScrobbling()">Authorize Scrobbling</button>
|
||||
<span class="setting-help-text" id="lastfm-scrobble-status"></span>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="lastfm-api-key"
|
||||
placeholder="Last.fm API Key">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Secret:</label>
|
||||
<input type="password" id="lastfm-api-secret"
|
||||
placeholder="Last.fm API Secret (required for scrobbling)">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your API key and secret from <a
|
||||
href="https://www.last.fm/api/account/create" target="_blank"
|
||||
style="color: #d51007;">Last.fm API Account</a></div>
|
||||
<div class="callback-help">API key: used for metadata enrichment. API secret: required for scrobbling.</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 12px;">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="lastfm-scrobble-enabled">
|
||||
Scrobble plays to Last.fm
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-actions" id="lastfm-scrobble-actions">
|
||||
<button class="test-button" onclick="authorizeLastfmScrobbling()">Authorize Scrobbling</button>
|
||||
<span class="setting-help-text" id="lastfm-scrobble-status"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Genius Settings -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title genius-title">Genius</h4>
|
||||
<div class="form-group">
|
||||
<label>Client Access Token:</label>
|
||||
<input type="password" id="genius-access-token"
|
||||
placeholder="Genius Client Access Token">
|
||||
<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="callback-info">
|
||||
<div class="callback-help">Get your token from <a
|
||||
href="https://genius.com/api-clients" target="_blank"
|
||||
style="color: #ffff64;">Genius API Clients</a></div>
|
||||
<div class="callback-help">Generate a "Client Access Token" — no OAuth flow needed.</div>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>Client Access Token:</label>
|
||||
<input type="password" id="genius-access-token"
|
||||
placeholder="Genius Client Access Token">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your token from <a
|
||||
href="https://genius.com/api-clients" target="_blank"
|
||||
style="color: #ffff64;">Genius API Clients</a></div>
|
||||
<div class="callback-help">Generate a "Client Access Token" — no OAuth flow needed.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- AcoustID Settings -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title acoustid-title">AcoustID Verification</h4>
|
||||
<div class="form-group" style="margin-bottom: 12px;">
|
||||
<label class="checkbox-label"
|
||||
style="display: flex; align-items: center; gap: 8px; cursor: pointer;">
|
||||
<input type="checkbox" id="acoustid-enabled"
|
||||
style="width: 16px; height: 16px;">
|
||||
<span>Enable Download Verification</span>
|
||||
</label>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 4px; margin-left: 24px;">
|
||||
Verifies downloaded audio matches expected track using fingerprints
|
||||
<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;">
|
||||
<input type="checkbox" id="acoustid-enabled"
|
||||
style="width: 16px; height: 16px;">
|
||||
<span>Enable Download Verification</span>
|
||||
</label>
|
||||
<div style="color: #888; font-size: 0.8em; margin-top: 4px; margin-left: 24px;">
|
||||
Verifies downloaded audio matches expected track using fingerprints
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="acoustid-api-key" placeholder="AcoustID API Key">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your free API key from <a
|
||||
href="https://acoustid.org/new-application" target="_blank"
|
||||
style="color: #ba55d3;">AcoustID Applications</a></div>
|
||||
<div class="callback-help"
|
||||
style="opacity: 0.7; font-size: 0.85em; margin-top: 4px;">
|
||||
The fpcalc fingerprint tool is automatically downloaded if needed.
|
||||
Failed verifications move files to Quarantine folder.
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="acoustid-api-key" placeholder="AcoustID API Key">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your free API key from <a
|
||||
href="https://acoustid.org/new-application" target="_blank"
|
||||
style="color: #ba55d3;">AcoustID Applications</a></div>
|
||||
<div class="callback-help"
|
||||
style="opacity: 0.7; font-size: 0.85em; margin-top: 4px;">
|
||||
The fpcalc fingerprint tool is automatically downloaded if needed.
|
||||
Failed verifications move files to Quarantine folder.
|
||||
</div>
|
||||
</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>
|
||||
<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>
|
||||
|
||||
<!-- ListenBrainz Settings -->
|
||||
<div class="api-service-frame">
|
||||
<h4 class="service-title listenbrainz-title">ListenBrainz</h4>
|
||||
<div class="form-group">
|
||||
<label>API Base URL:</label>
|
||||
<input type="text" id="listenbrainz-base-url"
|
||||
placeholder="Leave empty for official (api.listenbrainz.org)">
|
||||
<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="form-group">
|
||||
<label>User Token:</label>
|
||||
<input type="password" id="listenbrainz-token"
|
||||
placeholder="ListenBrainz User Token">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your token from <a
|
||||
href="https://listenbrainz.org/profile/" target="_blank"
|
||||
style="color: #eb743b;">ListenBrainz Settings</a></div>
|
||||
<div class="callback-help">Self-hosted? Enter your server URL (e.g. http://localhost:8093)</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 12px;">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="listenbrainz-scrobble-enabled">
|
||||
Scrobble plays to ListenBrainz
|
||||
</label>
|
||||
<div class="stg-service-body">
|
||||
<div class="form-group">
|
||||
<label>API Base URL:</label>
|
||||
<input type="text" id="listenbrainz-base-url"
|
||||
placeholder="Leave empty for official (api.listenbrainz.org)">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>User Token:</label>
|
||||
<input type="password" id="listenbrainz-token"
|
||||
placeholder="ListenBrainz User Token">
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">Get your token from <a
|
||||
href="https://listenbrainz.org/profile/" target="_blank"
|
||||
style="color: #eb743b;">ListenBrainz Settings</a></div>
|
||||
<div class="callback-help">Self-hosted? Enter your server URL (e.g. http://localhost:8093)</div>
|
||||
</div>
|
||||
<div class="form-group" style="margin-top: 12px;">
|
||||
<label class="checkbox-label">
|
||||
<input type="checkbox" id="listenbrainz-scrobble-enabled">
|
||||
Scrobble plays to ListenBrainz
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hydrabase P2P Metadata -->
|
||||
<div class="api-service-frame" data-stg="connections">
|
||||
<h4 class="service-title" style="color: #00b4d8;">Hydrabase</h4>
|
||||
<input type="hidden" id="hydrabase-enabled" value="false">
|
||||
<div class="form-group">
|
||||
<label>WebSocket URL:</label>
|
||||
<input type="text" id="hydrabase-url" placeholder="ws://localhost:4545">
|
||||
<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="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="hydrabase-api-key" placeholder="Hydrabase API Key">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label class="checkbox-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 0;">
|
||||
<input type="checkbox" id="hydrabase-auto-connect" style="width: 16px; height: 16px;">
|
||||
<span>Auto-connect on startup</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">P2P metadata network. When enabled and connected, replaces Spotify/iTunes as the primary metadata source for searches.</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="test-button" id="hydrabase-connect-btn" onclick="toggleHydrabaseFromSettings()">Connect</button>
|
||||
<span id="hydrabase-settings-status" style="font-size: 0.82em; color: rgba(255,255,255,0.4); margin-left: 8px;"></span>
|
||||
<div class="stg-service-body">
|
||||
<input type="hidden" id="hydrabase-enabled" value="false">
|
||||
<div class="form-group">
|
||||
<label>WebSocket URL:</label>
|
||||
<input type="text" id="hydrabase-url" placeholder="ws://localhost:4545">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>API Key:</label>
|
||||
<input type="password" id="hydrabase-api-key" placeholder="Hydrabase API Key">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 8px;">
|
||||
<label class="checkbox-label" style="display: flex; align-items: center; gap: 8px; cursor: pointer; padding: 0;">
|
||||
<input type="checkbox" id="hydrabase-auto-connect" style="width: 16px; height: 16px;">
|
||||
<span>Auto-connect on startup</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="callback-info">
|
||||
<div class="callback-help">P2P metadata network. When enabled and connected, replaces Spotify/iTunes as the primary metadata source for searches.</div>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button class="test-button" id="hydrabase-connect-btn" onclick="toggleHydrabaseFromSettings()">Connect</button>
|
||||
<span id="hydrabase-settings-status" style="font-size: 0.82em; color: rgba(255,255,255,0.4); margin-left: 8px;"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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