565 lines
No EOL
30 KiB
HTML
565 lines
No EOL
30 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>SoulSync - Music Sync & Manager</title>
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
</head>
|
|
<body>
|
|
<div class="main-container">
|
|
<!-- Sidebar - Always Visible -->
|
|
<div class="sidebar">
|
|
<!-- Header Section -->
|
|
<div class="sidebar-header">
|
|
<h1 class="app-name">SoulSync</h1>
|
|
<p class="app-subtitle">Music Sync & Manager</p>
|
|
</div>
|
|
|
|
<!-- Navigation Section -->
|
|
<nav class="sidebar-nav">
|
|
<button class="nav-button active" data-page="dashboard">
|
|
<span class="nav-icon">📊</span>
|
|
<span class="nav-text">Dashboard</span>
|
|
</button>
|
|
<button class="nav-button" data-page="sync">
|
|
<span class="nav-icon">🔄</span>
|
|
<span class="nav-text">Sync</span>
|
|
</button>
|
|
<button class="nav-button" data-page="downloads">
|
|
<span class="nav-icon">📥</span>
|
|
<span class="nav-text">Search</span>
|
|
</button>
|
|
<button class="nav-button" data-page="artists">
|
|
<span class="nav-icon">🎵</span>
|
|
<span class="nav-text">Artists</span>
|
|
</button>
|
|
<button class="nav-button" data-page="settings">
|
|
<span class="nav-icon">⚙️</span>
|
|
<span class="nav-text">Settings</span>
|
|
</button>
|
|
</nav>
|
|
|
|
<!-- Spacer -->
|
|
<div class="sidebar-spacer"></div>
|
|
|
|
<!-- Media Player Section -->
|
|
<div class="media-player" id="media-player">
|
|
<!-- Loading Animation -->
|
|
<div class="loading-animation hidden" id="loading-animation">
|
|
<div class="loading-bar">
|
|
<div class="loading-progress"></div>
|
|
</div>
|
|
<div class="loading-text">0%</div>
|
|
</div>
|
|
|
|
<!-- Header (always visible) -->
|
|
<div class="media-header">
|
|
<div class="media-info">
|
|
<div class="track-title" id="track-title">No track</div>
|
|
<div class="artist-name" id="artist-name">Unknown Artist</div>
|
|
</div>
|
|
<button class="play-button" id="play-button" disabled>▷</button>
|
|
</div>
|
|
|
|
<!-- Expanded Content (hidden initially) -->
|
|
<div class="media-expanded hidden" id="media-expanded">
|
|
<div class="album-name" id="album-name">Unknown Album</div>
|
|
<div class="media-controls">
|
|
<div class="volume-control">
|
|
<span class="volume-icon">🔊</span>
|
|
<input type="range" class="volume-slider" id="volume-slider" min="0" max="100" value="70">
|
|
</div>
|
|
<button class="stop-button" id="stop-button" disabled>⏹</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- No Track Message -->
|
|
<div class="no-track-message" id="no-track-message">
|
|
Start playing music to see controls
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Crypto Donation Section -->
|
|
<div class="crypto-donation">
|
|
<div class="donation-header">
|
|
<span class="donation-title">Support Development</span>
|
|
<button class="toggle-button" id="donation-toggle">Show</button>
|
|
</div>
|
|
<div class="donation-addresses hidden" id="donation-addresses">
|
|
<div class="donation-item" onclick="openKofi()">
|
|
<span class="donation-name">Ko-fi</span>
|
|
<span class="donation-link">Click to open</span>
|
|
</div>
|
|
<div class="donation-item" onclick="copyAddress('3JVWrRSkozAQSmw5DXYVxYKsM9bndPTqdS', 'Bitcoin')">
|
|
<span class="donation-name">Bitcoin</span>
|
|
<span class="donation-address">3JVWrR...dPTqdS</span>
|
|
</div>
|
|
<div class="donation-item" onclick="copyAddress('0x343fC48c2cd1C6332b0df9a58F86e6520a026AC5', 'Ethereum')">
|
|
<span class="donation-name">Ethereum</span>
|
|
<span class="donation-address">0x343f...026AC5</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Version Section -->
|
|
<div class="version-section">
|
|
<button class="version-button" onclick="showVersionInfo()">v.0.65</button>
|
|
</div>
|
|
|
|
<!-- Status Section -->
|
|
<div class="status-section">
|
|
<h4 class="status-title">Service Status</h4>
|
|
<div class="status-indicator" id="spotify-indicator">
|
|
<span class="status-dot disconnected"></span>
|
|
<span class="status-name">Spotify</span>
|
|
</div>
|
|
<div class="status-indicator" id="media-server-indicator">
|
|
<span class="status-dot disconnected"></span>
|
|
<span class="status-name" id="media-server-name">Plex</span>
|
|
</div>
|
|
<div class="status-indicator" id="soulseek-indicator">
|
|
<span class="status-dot disconnected"></span>
|
|
<span class="status-name">Soulseek</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Content Area -->
|
|
<div class="main-content">
|
|
<!-- Dashboard Page -->
|
|
<div class="page active" id="dashboard-page">
|
|
<div class="page-header">
|
|
<h2>Dashboard</h2>
|
|
</div>
|
|
<div class="dashboard-content">
|
|
<!-- Activity Feed -->
|
|
<div class="activity-section">
|
|
<h3>Recent Activity</h3>
|
|
<div class="activity-feed" id="activity-feed">
|
|
<div class="activity-item">
|
|
<span class="activity-time">Just now</span>
|
|
<span class="activity-text">Web UI initialized</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Statistics Cards -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card">
|
|
<div class="stat-value">0</div>
|
|
<div class="stat-label">Tracks Synced</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">0</div>
|
|
<div class="stat-label">Downloads</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">3</div>
|
|
<div class="stat-label">Services</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sync Page -->
|
|
<div class="page" id="sync-page">
|
|
<div class="page-header">
|
|
<h2>Sync</h2>
|
|
</div>
|
|
<div class="sync-content">
|
|
<div class="playlist-section">
|
|
<h3>Select Playlist</h3>
|
|
<select class="playlist-selector" id="playlist-selector">
|
|
<option value="">Loading playlists...</option>
|
|
</select>
|
|
<button class="sync-button" id="sync-button">Start Sync</button>
|
|
</div>
|
|
<div class="sync-progress hidden" id="sync-progress">
|
|
<div class="progress-bar">
|
|
<div class="progress-fill"></div>
|
|
</div>
|
|
<div class="progress-text">0% Complete</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Downloads Page -->
|
|
<div class="page" id="downloads-page">
|
|
<!--
|
|
This top-level container replicates the QSplitter from downloads.py,
|
|
creating the two-panel layout for the page.
|
|
-->
|
|
<div class="downloads-content">
|
|
|
|
<!-- ======================================================= -->
|
|
<!-- == LEFT PANEL: Search, Filters, and Results == -->
|
|
<!-- ======================================================= -->
|
|
<div class="downloads-main-panel">
|
|
|
|
<!-- Header: Replicates create_elegant_header() -->
|
|
<div class="downloads-header">
|
|
<h2 class="downloads-title">🎵 Music Downloads</h2>
|
|
<p class="downloads-subtitle">Search, discover, and download high-quality music</p>
|
|
</div>
|
|
|
|
<!-- Search Bar: Replicates create_elegant_search_bar() -->
|
|
<div class="search-bar-container">
|
|
<input type="text" id="downloads-search-input" placeholder="Search for music... (e.g., 'Virtual Mage', 'Queen Bohemian Rhapsody')">
|
|
<button id="downloads-cancel-btn" class="hidden">✕ Cancel</button>
|
|
<button id="downloads-search-btn">🔍 Search</button>
|
|
</div>
|
|
|
|
<!-- Search Status Bar -->
|
|
<div class="search-status-container">
|
|
<div class="spinner-animation hidden"></div>
|
|
<p id="search-status-text">Ready to search • Enter artist, song, or album name</p>
|
|
<div class="dots-animation hidden"></div>
|
|
</div>
|
|
|
|
<!-- Search Results Area: Replicates the QScrollArea -->
|
|
<div class="search-results-container">
|
|
<div class="search-results-header">
|
|
<h3>Search Results</h3>
|
|
</div>
|
|
<div class="search-results-scroll-area" id="search-results-area">
|
|
<!--
|
|
The placeholder search results have been removed.
|
|
This area will now be populated by JavaScript based on API responses.
|
|
-->
|
|
<div class="search-results-placeholder">
|
|
<p>Your search results will appear here.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ======================================================= -->
|
|
<!-- == RIGHT PANEL: Controls and Download Queue == -->
|
|
<!-- ======================================================= -->
|
|
<div class="downloads-side-panel">
|
|
|
|
<!-- Controls Panel: Replicates create_collapsible_controls_panel() -->
|
|
<div class="controls-panel">
|
|
<h3 class="controls-panel__header">Download Manager</h3>
|
|
<div class="controls-panel__stats">
|
|
<p id="active-downloads-label">• Active Downloads: 0</p>
|
|
<p id="finished-downloads-label">• Finished Downloads: 0</p>
|
|
</div>
|
|
<div class="controls-panel__actions">
|
|
<button class="controls-panel__clear-btn">🗑️ Clear Completed</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Download Queue: Replicates TabbedDownloadManager -->
|
|
<div class="download-manager">
|
|
<div class="download-manager__tabs">
|
|
<button class="tab-btn active" data-tab="active-queue">Download Queue (0)</button>
|
|
<button class="tab-btn" data-tab="finished-queue">Finished (0)</button>
|
|
</div>
|
|
<div class="download-manager__content">
|
|
|
|
<!-- Active Queue -->
|
|
<div class="download-queue active" id="active-queue">
|
|
<div class="download-queue__empty-message">No active downloads.</div>
|
|
</div>
|
|
|
|
<!-- Finished Queue -->
|
|
<div class="download-queue" id="finished-queue">
|
|
<div class="download-queue__empty-message">No finished downloads.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Artists Page -->
|
|
<div class="page" id="artists-page">
|
|
<div class="page-header">
|
|
<h2>Artists</h2>
|
|
</div>
|
|
<div class="artists-content">
|
|
<div class="artists-grid" id="artists-grid">
|
|
<!-- Artists will be populated here -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Settings Page -->
|
|
<div class="page" id="settings-page">
|
|
<div class="page-header">
|
|
<h2>Settings</h2>
|
|
</div>
|
|
<div class="settings-content">
|
|
<!-- Two Column Layout -->
|
|
<div class="settings-columns">
|
|
<!-- Left Column - API Configuration -->
|
|
<div class="settings-left-column">
|
|
<!-- API Configuration -->
|
|
<div class="settings-group">
|
|
<h3>API Configuration</h3>
|
|
|
|
<!-- 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>
|
|
<div class="form-group">
|
|
<label>Client Secret:</label>
|
|
<input type="password" id="spotify-client-secret" placeholder="Spotify Client Secret">
|
|
</div>
|
|
<div class="callback-info">
|
|
<div class="callback-label">Required Redirect URI:</div>
|
|
<div class="callback-url">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>
|
|
|
|
<!-- Tidal Settings -->
|
|
<div class="api-service-frame">
|
|
<h4 class="service-title tidal-title">Tidal</h4>
|
|
<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="callback-info">
|
|
<div class="callback-label">Required Redirect URI:</div>
|
|
<div class="callback-url">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>
|
|
|
|
<!-- Soulseek Settings -->
|
|
<div class="api-service-frame">
|
|
<h4 class="service-title soulseek-title">Soulseek</h4>
|
|
<div class="form-group">
|
|
<label>slskd URL:</label>
|
|
<div class="path-input-group">
|
|
<input type="url" id="soulseek-url" placeholder="http://localhost:5030">
|
|
<button class="detect-button" onclick="autoDetectSlskd()">Auto-detect</button>
|
|
</div>
|
|
</div>
|
|
<div class="form-group">
|
|
<label>API Key:</label>
|
|
<input type="password" id="soulseek-api-key" placeholder="Slskd API Key">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Test Connection Buttons -->
|
|
<div class="api-test-buttons">
|
|
<button class="test-button" onclick="testConnection('spotify')">Test Spotify</button>
|
|
<button class="test-button" onclick="testConnection('tidal')">Test Tidal</button>
|
|
<button class="test-button" onclick="testConnection('soulseek')">Test Soulseek</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Server Connections -->
|
|
<div class="settings-group">
|
|
<h3>Server Connections</h3>
|
|
|
|
<!-- Server Toggle Buttons -->
|
|
<div class="server-toggle-container">
|
|
<button class="server-toggle-btn active" id="plex-toggle" onclick="toggleServer('plex')">Plex</button>
|
|
<button class="server-toggle-btn" id="jellyfin-toggle" onclick="toggleServer('jellyfin')">Jellyfin</button>
|
|
</div>
|
|
|
|
<!-- Plex Settings -->
|
|
<div class="server-config-container" id="plex-container">
|
|
<div class="form-group">
|
|
<label>Plex Server URL:</label>
|
|
<input type="url" id="plex-url" placeholder="http://localhost:32400">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Plex Token:</label>
|
|
<input type="password" id="plex-token" placeholder="X-Plex-Token">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button class="detect-button" onclick="autoDetectPlex()">Auto-detect</button>
|
|
<button class="test-button" onclick="testConnection('plex')">Test</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Jellyfin Settings -->
|
|
<div class="server-config-container hidden" id="jellyfin-container">
|
|
<div class="form-group">
|
|
<label>Jellyfin Server URL:</label>
|
|
<input type="url" id="jellyfin-url" placeholder="http://localhost:8096">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>API Key:</label>
|
|
<input type="password" id="jellyfin-api-key" placeholder="Jellyfin API Key">
|
|
</div>
|
|
<div class="form-actions">
|
|
<button class="detect-button" onclick="autoDetectJellyfin()">Auto-detect</button>
|
|
<button class="test-button" onclick="testConnection('jellyfin')">Test</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Server Test Button -->
|
|
<div class="server-test-section">
|
|
<button class="test-button server-test-btn" onclick="testConnection('server')">Test Server</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Right Column - Download Settings, Database, Metadata, Logging -->
|
|
<div class="settings-right-column">
|
|
<!-- Download Settings -->
|
|
<div class="settings-group">
|
|
<h3>Download Settings</h3>
|
|
|
|
<div class="form-group">
|
|
<label>Preferred Quality:</label>
|
|
<select id="preferred-quality">
|
|
<option value="flac">FLAC</option>
|
|
<option value="mp3_320">320 kbps MP3</option>
|
|
<option value="mp3_256">256 kbps MP3</option>
|
|
<option value="mp3_192">192 kbps MP3</option>
|
|
<option value="any">Any</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Slskd Download Dir:</label>
|
|
<div class="path-input-group">
|
|
<input type="text" id="download-path" placeholder="./downloads">
|
|
<button class="browse-button" onclick="browsePath('download')">Browse</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Matched Transfer Dir (Plex Music Dir?):</label>
|
|
<div class="path-input-group">
|
|
<input type="text" id="transfer-path" placeholder="./Transfer">
|
|
<button class="browse-button" onclick="browsePath('transfer')">Browse</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Database Settings -->
|
|
<div class="settings-group">
|
|
<h3>Database Settings</h3>
|
|
|
|
<div class="form-group">
|
|
<label>Concurrent Workers:</label>
|
|
<select id="max-workers">
|
|
<option value="3">3</option>
|
|
<option value="4">4</option>
|
|
<option value="5" selected>5</option>
|
|
<option value="6">6</option>
|
|
<option value="7">7</option>
|
|
<option value="8">8</option>
|
|
<option value="9">9</option>
|
|
<option value="10">10</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="help-text">Number of parallel threads for database updates. Higher values = faster updates but more server load.</div>
|
|
</div>
|
|
|
|
<!-- Metadata Enhancement Settings -->
|
|
<div class="settings-group">
|
|
<h3>🎵 Metadata Enhancement</h3>
|
|
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="metadata-enabled" checked>
|
|
Enable metadata enhancement with Spotify data
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="embed-album-art" checked>
|
|
Embed high-quality album art from Spotify
|
|
</label>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Supported Formats:</label>
|
|
<div class="supported-formats">MP3, FLAC, MP4/M4A, OGG</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Playlist Sync Settings -->
|
|
<div class="settings-group">
|
|
<h3>Playlist Sync Settings</h3>
|
|
|
|
<div class="form-group">
|
|
<label class="checkbox-label">
|
|
<input type="checkbox" id="create-backup" checked>
|
|
Create playlist backups before sync
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Logging Information (Read-only) -->
|
|
<div class="settings-group">
|
|
<h3>Logging Information</h3>
|
|
|
|
<div class="form-group">
|
|
<label>Log Level:</label>
|
|
<div class="readonly-field" id="log-level-display">DEBUG</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label>Log Path:</label>
|
|
<div class="readonly-field" id="log-path-display">logs/app.log</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Save Button -->
|
|
<div class="settings-actions">
|
|
<button class="save-button" id="save-settings">💾 Save Settings</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading Overlay -->
|
|
<div class="loading-overlay hidden" id="loading-overlay">
|
|
<div class="loading-spinner"></div>
|
|
<div class="loading-message">Processing...</div>
|
|
</div>
|
|
|
|
<!-- Toast Notifications -->
|
|
<div class="toast-container" id="toast-container"></div>
|
|
|
|
<!-- Version Info Modal -->
|
|
<div class="version-modal-overlay hidden" id="version-modal-overlay" onclick="closeVersionModal()">
|
|
<div class="version-modal" onclick="event.stopPropagation()">
|
|
<!-- Header -->
|
|
<div class="version-modal-header">
|
|
<h2 class="version-modal-title">What's New in SoulSync</h2>
|
|
<div class="version-modal-subtitle">Version 0.65 - Tidal Playlist Integration</div>
|
|
</div>
|
|
|
|
<!-- Content Area with Scroll -->
|
|
<div class="version-modal-content">
|
|
<div class="version-content-container" id="version-content-container">
|
|
<!-- Content will be populated by JavaScript -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="version-modal-footer">
|
|
<button class="version-modal-close" onclick="closeVersionModal()">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="{{ url_for('static', filename='script.js') }}"></script>
|
|
</body>
|
|
</html> |