new: added default firebase instance, reworked settings layout

This commit is contained in:
Julien Maille 2025-12-29 23:02:59 +01:00
parent 2a98654e54
commit 060d4762cc
6 changed files with 338 additions and 188 deletions

View file

@ -282,190 +282,212 @@
<div id="page-settings" class="page">
<h2 class="section-title">Settings</h2>
<div class="settings-list">
<div class="setting-item">
<div class="info">
<span class="label">Theme</span>
<span class="description">Choose your preferred color scheme</span>
</div>
</div>
<div class="theme-picker" id="theme-picker">
<div class="theme-option" data-theme="system">System</div>
<div class="theme-option" data-theme="light">Light</div>
<div class="theme-option" data-theme="dark">Dark</div>
<div class="theme-option" data-theme="monochrome">Black</div>
<div class="theme-option" data-theme="ocean">Ocean</div>
<div class="theme-option" data-theme="purple">Purple</div>
<div class="theme-option" data-theme="forest">Forest</div>
<div class="theme-option" data-theme="custom">Custom</div>
</div>
<div class="custom-theme-editor" id="custom-theme-editor">
<h4>Custom Theme</h4>
<div class="theme-color-grid" id="theme-color-grid"></div>
<div class="theme-actions">
<button class="btn-secondary" id="apply-custom-theme">Apply Theme</button>
<button class="btn-secondary" id="reset-custom-theme">Reset</button>
</div>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Album Cover Background</span>
<span class="description">Use the album cover as a blurred background on album pages and as primary color</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="album-background-toggle">
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Last.fm Scrobbling</span>
<span class="description" id="lastfm-status">Connect your Last.fm account to scrobble tracks</span>
</div>
<div id="lastfm-controls">
<button id="lastfm-connect-btn" class="btn-secondary">Connect Last.fm</button>
</div>
</div>
<div class="setting-item" id="lastfm-toggle-setting" style="display: none;">
<div class="info">
<span class="label">Enable Scrobbling</span>
<span class="description">Automatically scrobble played tracks</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="lastfm-toggle">
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Firebase Configuration</span>
<span class="description">Paste your Firebase Config JSON here to enable sync.</span>
</div>
<div style="display: flex; flex-direction: column; gap: 0.5rem; width: 100%; max-width: 400px;">
<textarea id="firebase-config-input" class="template-input" rows="5" placeholder='{ "apiKey": "...", "authDomain": "...", ... }'></textarea>
<div style="display: flex; gap: 0.5rem;">
<button id="save-firebase-config-btn" class="btn-secondary">Save & Reload</button>
<button id="share-firebase-config-btn" class="btn-secondary">Share</button>
<button id="clear-firebase-config-btn" class="btn-secondary danger">Clear Config</button>
</div>
</div>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Sync & Backup (Beta)</span>
<span class="description" id="firebase-status">Sync your library across devices</span>
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem; align-items: center;">
<img id="firebase-user-avatar" src="" style="width: 24px; height: 24px; border-radius: 50%; display: none;" onerror="this.style.display='none'" onload="this.style.display='block'">
<span id="firebase-user-name" style="font-size: 0.85rem; color: var(--foreground);"></span>
</div>
</div>
<div id="firebase-controls">
<button id="firebase-connect-btn" class="btn-secondary">Connect with Google</button>
</div>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Audio Quality</span>
<span class="description">Quality for streaming and downloads</span>
</div>
<select id="quality-setting">
<option value="LOSSLESS">FLAC (Lossless)</option>
<option value="HIGH">AAC 320kbps</option>
<option value="LOW">AAC 96kbps</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Now Playing View Mode</span>
<span class="description">Choose what shows when you click the album art</span>
</div>
<select id="now-playing-mode">
<option value="album">Show Album</option>
<option value="cover">Enlarged Cover</option>
<option value="lyrics">Lyrics Panel</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Track List Actions</span>
<span class="description">Choose between a dropdown menu or inline buttons for track actions</span>
</div>
<select id="track-list-actions-mode">
<option value="dropdown">Dropdown Menu</option>
<option value="inline">Inline Buttons</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Download Lyrics</span>
<span class="description">Include .lrc files when downloading tracks/albums</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="download-lyrics-toggle">
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Gapless Playback</span>
<span class="description">Play audio without interruption between tracks</span>
</div>
<label class="toggle-switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Filename Template</span>
<span class="description">Customize download filenames. Available: {trackNumber}, {artist}, {title}, {album}</span>
</div>
<input type="text" id="filename-template" class="template-input" placeholder="{trackNumber} - {artist} - {title}">
</div>
<div class="setting-item">
<div class="info">
<span class="label">ZIP Folder Template</span>
<span class="description">Customize album folder names. Available: {albumTitle}, {albumArtist}, {year}</span>
</div>
<input type="text" id="zip-folder-template" class="template-input" placeholder="{albumTitle} - {albumArtist} - monochrome.tf">
</div>
<div class="setting-item">
<div class="info">
<span class="label">Keyboard Shortcuts</span>
<span class="description">View available keyboard shortcuts</span>
</div>
<button id="show-shortcuts-btn" class="btn-secondary">Show Shortcuts</button>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Cache</span>
<span class="description" id="cache-info">Stores API responses to reduce requests</span>
</div>
<button id="clear-cache-btn" class="btn-secondary">Clear Cache</button>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Backup & Restore</span>
<span class="description">Export or import your library and history as JSON</span>
</div>
<div style="display: flex; gap: 0.5rem;">
<button id="export-library-btn" class="btn-secondary">Export</button>
<button id="import-library-btn" class="btn-secondary">Import</button>
<input type="file" id="import-library-input" style="display: none;" accept=".json">
</div>
</div>
<div id="api-instance-manager">
<div class="setting-item" style="padding-bottom: 1rem; border: none;">
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">API Instances</span>
<span class="description">Manage and prioritize API instances. Automatically sorted by speed.</span>
<span class="label">Theme</span>
<span class="description">Choose your preferred color scheme</span>
</div>
<button id="refresh-speed-test-btn" class="btn-secondary">Refresh Speed Test</button>
</div>
<ul id="api-instance-list"></ul>
<div class="theme-picker" id="theme-picker">
<div class="theme-option" data-theme="system">System</div>
<div class="theme-option" data-theme="light">Light</div>
<div class="theme-option" data-theme="dark">Dark</div>
<div class="theme-option" data-theme="monochrome">Black</div>
<div class="theme-option" data-theme="ocean">Ocean</div>
<div class="theme-option" data-theme="purple">Purple</div>
<div class="theme-option" data-theme="forest">Forest</div>
<div class="theme-option" data-theme="custom">Custom</div>
</div>
<div class="custom-theme-editor" id="custom-theme-editor">
<h4>Custom Theme</h4>
<div class="theme-color-grid" id="theme-color-grid"></div>
<div class="theme-actions">
<button class="btn-secondary" id="apply-custom-theme">Apply Theme</button>
<button class="btn-secondary" id="reset-custom-theme">Reset</button>
</div>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Album Cover Background</span>
<span class="description">Use the album cover as a blurred background on album pages and as primary color</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="album-background-toggle">
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Last.fm Scrobbling</span>
<span class="description" id="lastfm-status">Connect your Last.fm account to scrobble tracks</span>
</div>
<div id="lastfm-controls">
<button id="lastfm-connect-btn" class="btn-secondary">Connect Last.fm</button>
</div>
</div>
<div class="setting-item" id="lastfm-toggle-setting" style="display: none;">
<div class="info">
<span class="label">Enable Scrobbling</span>
<span class="description">Automatically scrobble played tracks</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="lastfm-toggle">
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Firebase Configuration</span>
<span class="description">Manage your database connection.</span>
</div>
<div class="firebase-settings-wrapper">
<button id="toggle-firebase-config-btn" class="btn-secondary">Advanced: Custom Configuration</button>
<div id="custom-firebase-config-container" class="custom-firebase-config">
<p class="config-help-text">Default shared instance is active. <a href="firebase-setup.md" target="_blank" class="text-link">Override below only if needed.</a></p>
<textarea id="firebase-config-input" class="template-input" rows="5" placeholder='{ "apiKey": "...", "authDomain": "...", ... }'></textarea>
<div class="firebase-controls-container">
<button id="save-firebase-config-btn" class="btn-secondary">Save & Reload</button>
<button id="share-firebase-config-btn" class="btn-secondary">Share</button>
<button id="clear-firebase-config-btn" class="btn-secondary danger">Clear Config</button>
</div>
</div>
</div>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Sync & Backup (Beta)</span>
<span class="description" id="firebase-status">Sync your library across devices</span>
<div style="display: flex; gap: 0.5rem; margin-top: 0.5rem; align-items: center;">
<img id="firebase-user-avatar" src="" style="width: 24px; height: 24px; border-radius: 50%; display: none;" onerror="this.style.display='none'" onload="this.style.display='block'">
<span id="firebase-user-name" style="font-size: 0.85rem; color: var(--foreground);"></span>
</div>
</div>
<div id="firebase-controls">
<button id="firebase-connect-btn" class="btn-secondary">Connect with Google</button>
<button id="firebase-clear-cloud-btn" class="btn-secondary danger" style="display: none; margin-top: 0.5rem;">Clear Cloud Data</button>
</div>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Audio Quality</span>
<span class="description">Quality for streaming and downloads</span>
</div>
<select id="quality-setting">
<option value="LOSSLESS">FLAC (Lossless)</option>
<option value="HIGH">AAC 320kbps</option>
<option value="LOW">AAC 96kbps</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Gapless Playback</span>
<span class="description">Play audio without interruption between tracks</span>
</div>
<label class="toggle-switch">
<input type="checkbox" checked>
<span class="slider"></span>
</label>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Now Playing View Mode</span>
<span class="description">Choose what shows when you click the album art</span>
</div>
<select id="now-playing-mode">
<option value="album">Show Album</option>
<option value="cover">Enlarged Cover</option>
<option value="lyrics">Lyrics Panel</option>
</select>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Track List Actions</span>
<span class="description">Choose between a dropdown menu or inline buttons for track actions</span>
</div>
<select id="track-list-actions-mode">
<option value="dropdown">Dropdown Menu</option>
<option value="inline">Inline Buttons</option>
</select>
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Download Lyrics</span>
<span class="description">Include .lrc files when downloading tracks/albums</span>
</div>
<label class="toggle-switch">
<input type="checkbox" id="download-lyrics-toggle">
<span class="slider"></span>
</label>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Filename Template</span>
<span class="description">Customize download filenames. Available: {trackNumber}, {artist}, {title}, {album}</span>
</div>
<input type="text" id="filename-template" class="template-input" placeholder="{trackNumber} - {artist} - {title}">
</div>
<div class="setting-item">
<div class="info">
<span class="label">ZIP Folder Template</span>
<span class="description">Customize album folder names. Available: {albumTitle}, {albumArtist}, {year}</span>
</div>
<input type="text" id="zip-folder-template" class="template-input" placeholder="{albumTitle} - {albumArtist} - monochrome.tf">
</div>
</div>
<div class="settings-group">
<div class="setting-item">
<div class="info">
<span class="label">Keyboard Shortcuts</span>
<span class="description">View available keyboard shortcuts</span>
</div>
<button id="show-shortcuts-btn" class="btn-secondary">Show Shortcuts</button>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Cache</span>
<span class="description" id="cache-info">Stores API responses to reduce requests</span>
</div>
<button id="clear-cache-btn" class="btn-secondary">Clear Cache</button>
</div>
<div class="setting-item">
<div class="info">
<span class="label">Backup & Restore</span>
<span class="description">Export or import your library and history as JSON</span>
</div>
<div style="display: flex; gap: 0.5rem;">
<button id="export-library-btn" class="btn-secondary">Export</button>
<button id="import-library-btn" class="btn-secondary">Import</button>
<input type="file" id="import-library-input" style="display: none;" accept=".json">
</div>
</div>
<div id="api-instance-manager">
<div class="setting-item" style="padding-bottom: 1rem; border: none;">
<div class="info">
<span class="label">API Instances</span>
<span class="description">Manage and prioritize API instances. Automatically sorted by speed.</span>
</div>
<button id="refresh-speed-test-btn" class="btn-secondary">Refresh Speed Test</button>
</div>
<ul id="api-instance-list"></ul>
</div>
</div>
</div>
</div>

View file

@ -58,6 +58,7 @@ export class AuthManager {
updateUI(user) {
const connectBtn = document.getElementById('firebase-connect-btn');
const clearDataBtn = document.getElementById('firebase-clear-cloud-btn');
const statusText = document.getElementById('firebase-status');
const userAvatar = document.getElementById('firebase-user-avatar');
const userName = document.getElementById('firebase-user-name');
@ -70,6 +71,8 @@ export class AuthManager {
connectBtn.classList.add('danger');
connectBtn.onclick = () => this.signOut();
if (clearDataBtn) clearDataBtn.style.display = 'block';
if (statusText) statusText.textContent = `Signed in as ${user.email}`;
// Optional: Show user info if elements exist
@ -81,6 +84,8 @@ export class AuthManager {
connectBtn.classList.remove('danger');
connectBtn.onclick = () => this.signInWithGoogle();
if (clearDataBtn) clearDataBtn.style.display = 'none';
if (statusText) statusText.textContent = 'Sync your library across devices';
if (userAvatar) userAvatar.src = ''; // Placeholder or clear

View file

@ -10,6 +10,15 @@ let provider = null;
const STORAGE_KEY = 'monochrome-firebase-config';
const DEFAULT_CONFIG = {
apiKey: "AIzaSyDPU-unAjuLtQJt4IkGS5faG50UCF7lYyA",
authDomain: "monochrome-database.firebaseapp.com",
projectId: "monochrome-database",
storageBucket: "monochrome-database.firebasestorage.app",
messagingSenderId: "895657412760",
appId: "1:895657412760:web:e81c5044c7f4e9b799e8ed"
};
function getStoredConfig() {
try {
const stored = localStorage.getItem(STORAGE_KEY);
@ -21,19 +30,21 @@ function getStoredConfig() {
}
// Attempt to initialize on load
const config = getStoredConfig();
const storedConfig = getStoredConfig();
const config = storedConfig || DEFAULT_CONFIG;
if (config) {
try {
app = initializeApp(config);
auth = getAuth(app);
database = getDatabase(app);
provider = new GoogleAuthProvider();
console.log("Firebase initialized from saved config");
console.log("Firebase initialized from " + (storedConfig ? "saved" : "default") + " config");
} catch (error) {
console.error("Error initializing Firebase from saved config:", error);
console.error("Error initializing Firebase:", error);
}
} else {
console.log("No Firebase config found in local storage.");
console.log("No Firebase config found.");
}
export function saveFirebaseConfig(configObj) {
@ -113,6 +124,22 @@ export function initializeFirebaseSettingsUI() {
const saveFirebaseConfigBtn = document.getElementById('save-firebase-config-btn');
const clearFirebaseConfigBtn = document.getElementById('clear-firebase-config-btn');
const shareFirebaseConfigBtn = document.getElementById('share-firebase-config-btn');
const toggleFirebaseConfigBtn = document.getElementById('toggle-firebase-config-btn');
const customFirebaseConfigContainer = document.getElementById('custom-firebase-config-container');
// Toggle Button Logic
if (toggleFirebaseConfigBtn && customFirebaseConfigContainer) {
toggleFirebaseConfigBtn.addEventListener('click', () => {
const isVisible = customFirebaseConfigContainer.classList.contains('visible');
if (isVisible) {
customFirebaseConfigContainer.classList.remove('visible');
toggleFirebaseConfigBtn.textContent = 'Advanced: Custom Configuration';
} else {
customFirebaseConfigContainer.classList.add('visible');
toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration';
}
});
}
// Populate current config
if (firebaseConfigInput) {
@ -120,6 +147,11 @@ export function initializeFirebaseSettingsUI() {
if (currentConfig) {
try {
firebaseConfigInput.value = JSON.stringify(JSON.parse(currentConfig), null, 2);
// If custom config exists, show the container
if (customFirebaseConfigContainer && toggleFirebaseConfigBtn) {
customFirebaseConfigContainer.classList.add('visible');
toggleFirebaseConfigBtn.textContent = 'Hide Custom Configuration';
}
} catch (e) {
firebaseConfigInput.value = currentConfig;
}
@ -192,7 +224,7 @@ export function initializeFirebaseSettingsUI() {
// Clear Button
if (clearFirebaseConfigBtn) {
clearFirebaseConfigBtn.addEventListener('click', () => {
if (confirm('Are you sure you want to clear the Firebase configuration? Sync will stop.')) {
if (confirm('Are you sure you want to remove the custom configuration? The app will revert to the shared default database.')) {
clearFirebaseConfig();
window.location.reload();
}

View file

@ -206,7 +206,16 @@ export class SyncManager {
const itemRef = child(this.userRef, path);
if (isAdded) {
await set(itemRef, item);
// Minify to ensure consistency and reduce bandwidth
// We use the db helper to ensure consistent structure
const minified = db._minifyItem(type, item);
// Ensure addedAt is present. If the passed item didn't have it (e.g. from player),
// we add it now. Ideally this matches local DB time, but a small diff is negligible.
const entry = {
...minified,
addedAt: item.addedAt || minified.addedAt || Date.now()
};
await set(itemRef, entry);
} else {
await remove(itemRef);
}
@ -222,6 +231,13 @@ export class SyncManager {
console.error("Failed to sync history item:", error);
}
}
async clearCloudData() {
if (!this.user || !this.userRef) {
throw new Error("Not authenticated");
}
await remove(this.userRef);
}
}
export const syncManager = new SyncManager();

View file

@ -298,6 +298,19 @@ export function initializeSettings(scrobbler, player, api, ui) {
}
});
document.getElementById('firebase-clear-cloud-btn')?.addEventListener('click', async () => {
if (confirm('Are you sure you want to delete ALL your data from the cloud? This cannot be undone.')) {
try {
await syncManager.clearCloudData();
alert('Cloud data cleared successfully.');
authManager.signOut();
} catch (error) {
console.error('Failed to clear cloud data:', error);
alert('Failed to clear cloud data: ' + error.message);
}
}
});
// Backup & Restore
document.getElementById('export-library-btn')?.addEventListener('click', async () => {
const data = await db.exportData();

View file

@ -174,6 +174,16 @@ a {
text-decoration: none;
}
.text-link {
color: var(--primary);
text-decoration: underline;
cursor: pointer;
}
.text-link:hover {
color: var(--highlight);
}
kbd {
background-color: var(--secondary);
border: 1px solid var(--border);
@ -997,10 +1007,22 @@ body.has-page-background .track-item:hover {
max-width: 800px;
}
.settings-group {
border-bottom: 1px solid var(--border);
padding: var(--spacing-lg) 0;
display: flex;
flex-direction: column;
gap: var(--spacing-md);
}
.settings-group .setting-item {
border-bottom: none;
padding: 0;
}
.setting-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg) 0;
border-bottom: 1px solid var(--border);
gap: var(--spacing-lg);
@ -3200,4 +3222,44 @@ img:not([src]), img[src=''] {
filter: brightness(1.1);
}
/* Firebase Settings Styling */
.firebase-settings-wrapper {
display: flex;
flex-direction: column;
gap: 0.5rem;
width: 100%;
max-width: 400px;
align-items: flex-end;
}
.custom-firebase-config {
display: none;
flex-direction: column;
gap: 0.5rem;
margin-top: 0.5rem;
width: 100%;
}
.custom-firebase-config.visible {
display: flex;
}
.config-help-text {
font-size: 0.8rem;
opacity: 0.7;
margin-bottom: 0.25rem;
}
#firebase-controls {
text-align: end;
}
.firebase-controls-container {
display: flex;
gap: 0.5rem;
width: 100%;
}
#toggle-firebase-config-btn {
width: fit-content;
}