diff --git a/index.html b/index.html index 38a1f7c..400a4a6 100644 --- a/index.html +++ b/index.html @@ -3644,6 +3644,27 @@ +
+
+ Auto-Update App + Automatically reload when a new version is available +
+ +
+
+
+ Reset Local Data + Clear all local storage and cached data (does not affect cloud sync) +
+ +
Backup & Restore diff --git a/js/app.js b/js/app.js index 431b66f..d399923 100644 --- a/js/app.js +++ b/js/app.js @@ -1,6 +1,13 @@ //js/app.js import { LosslessAPI } from './api.js'; -import { apiSettings, themeManager, nowPlayingSettings, downloadQualitySettings, sidebarSettings } from './storage.js'; +import { + apiSettings, + themeManager, + nowPlayingSettings, + downloadQualitySettings, + sidebarSettings, + pwaUpdateSettings, +} from './storage.js'; import { UIRenderer } from './ui.js'; import { Player } from './player.js'; import { MultiScrobbler } from './multi-scrobbler.js'; @@ -1471,7 +1478,13 @@ document.addEventListener('DOMContentLoaded', async () => { } else { const updateSW = registerSW({ onNeedRefresh() { - showUpdateNotification(() => updateSW(true)); + if (pwaUpdateSettings.isAutoUpdateEnabled()) { + // Auto-update: immediately activate the new service worker + updateSW(true); + } else { + // Show notification with Update button and dismiss option + showUpdateNotification(() => updateSW(true)); + } }, onOfflineReady() { console.log('App ready to work offline'); @@ -1558,6 +1571,12 @@ document.addEventListener('DOMContentLoaded', async () => { }); function showUpdateNotification(updateCallback) { + // Remove any existing update notification + const existingNotification = document.querySelector('.update-notification'); + if (existingNotification) { + existingNotification.remove(); + } + const notification = document.createElement('div'); notification.className = 'update-notification'; notification.innerHTML = ` @@ -1565,7 +1584,15 @@ function showUpdateNotification(updateCallback) { Update Available

A new version of Monochrome is available.

- +
+ + +
`; document.body.appendChild(notification); @@ -1578,6 +1605,10 @@ function showUpdateNotification(updateCallback) { window.location.reload(); } }); + + document.getElementById('dismiss-update-btn').addEventListener('click', () => { + notification.remove(); + }); } function showMissingTracksNotification(missingTracks) { diff --git a/js/settings.js b/js/settings.js index 491a20c..39bf616 100644 --- a/js/settings.js +++ b/js/settings.js @@ -27,6 +27,7 @@ import { monoAudioSettings, exponentialVolumeSettings, audioEffectsSettings, + pwaUpdateSettings, } from './storage.js'; import { audioContextManager, EQ_PRESETS } from './audio-context.js'; import { getButterchurnPresets } from './visualizers/butterchurn.js'; @@ -1777,6 +1778,73 @@ export function initializeSettings(scrobbler, player, api, ui) { }); } + // PWA Auto-Update Toggle + const pwaAutoUpdateToggle = document.getElementById('pwa-auto-update-toggle'); + if (pwaAutoUpdateToggle) { + pwaAutoUpdateToggle.checked = pwaUpdateSettings.isAutoUpdateEnabled(); + pwaAutoUpdateToggle.addEventListener('change', (e) => { + pwaUpdateSettings.setAutoUpdateEnabled(e.target.checked); + }); + } + + // Reset Local Data Button + const resetLocalDataBtn = document.getElementById('reset-local-data-btn'); + if (resetLocalDataBtn) { + resetLocalDataBtn.addEventListener('click', async () => { + if ( + confirm( + 'WARNING: This will clear all local data including settings, cache, and library.\n\nAre you sure you want to continue?\n\n(Cloud-synced data will not be affected)' + ) + ) { + try { + // Clear all localStorage + const keysToPreserve = []; + // Optionally preserve certain keys if needed + + // Get all keys + const allKeys = Object.keys(localStorage); + + // Clear each key except preserved ones + allKeys.forEach((key) => { + if (!keysToPreserve.includes(key)) { + localStorage.removeItem(key); + } + }); + + // Clear IndexedDB - try to clear individual stores, fallback to deleting database + try { + const stores = ['tracks', 'albums', 'artists', 'playlists', 'settings', 'history']; + for (const storeName of stores) { + try { + await db.performTransaction(storeName, 'readwrite', (store) => store.clear()); + } catch (e) { + // Store might not exist, continue + } + } + } catch (dbError) { + console.log('Could not clear IndexedDB stores:', dbError); + // Try to delete the entire database as fallback + try { + const deleteRequest = indexedDB.deleteDatabase('monochrome-music'); + await new Promise((resolve, reject) => { + deleteRequest.onsuccess = resolve; + deleteRequest.onerror = reject; + }); + } catch (deleteError) { + console.log('Could not delete IndexedDB:', deleteError); + } + } + + alert('All local data has been cleared. The app will now reload.'); + window.location.reload(); + } catch (error) { + console.error('Failed to reset local data:', error); + alert('Failed to reset local data: ' + error.message); + } + } + }); + } + // Font Settings initializeFontSettings(); diff --git a/js/storage.js b/js/storage.js index c9bc7e3..9953e16 100644 --- a/js/storage.js +++ b/js/storage.js @@ -1703,3 +1703,20 @@ export const fontSettings = { })); }, }; + +export const pwaUpdateSettings = { + STORAGE_KEY: 'pwa-auto-update-enabled', + + isAutoUpdateEnabled() { + try { + // Default to true (auto-update) if not set + return localStorage.getItem(this.STORAGE_KEY) !== 'false'; + } catch { + return true; + } + }, + + setAutoUpdateEnabled(enabled) { + localStorage.setItem(this.STORAGE_KEY, enabled ? 'true' : 'false'); + }, +}; diff --git a/styles.css b/styles.css index d11761d..9487a92 100644 --- a/styles.css +++ b/styles.css @@ -3430,6 +3430,34 @@ input:checked + .slider::before { align-items: stretch; } +.update-notification-actions { + display: flex; + align-items: center; + gap: 0.5rem; + margin-top: 0.75rem; +} + +.update-notification-actions .btn-icon { + background: transparent; + border: 1px solid var(--border); + color: var(--muted-foreground); + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--radius); + cursor: pointer; + transition: all var(--transition); + flex-shrink: 0; +} + +.update-notification-actions .btn-icon:hover { + background: var(--secondary); + color: var(--foreground); + border-color: var(--foreground); +} + .close-shortcuts { background: transparent; border: none;