Commit 3238afb2 authored by Gemini's avatar Gemini Committed by Jakob Moser
Browse files

feat(Favorites): Implement client-side persistence using localStorage

This commit introduces client-side storage for song favorites. Favorite states are now saved to and loaded from the user's localStorage, ensuring persistence across sessions.

- Added helper functions in SongList.mjs to manage reading from and writing to localStorage.
- Modified song loading logic to initialize favorite states from localStorage.
- Updated the toggle favorite functionality to persist changes to localStorage.
- Ensured consistent block syntax for try...catch statements.
parent f4df333c
Loading
Loading
Loading
Loading
+62 −18
Original line number Diff line number Diff line
// SongList.mjs
// frontend/SongList.mjs
import SongItem from './SongItem.mjs';
import initialSongs from './mockData.mjs';

const FAVORITES_STORAGE_KEY = 'songAppFavorites'; // Schlüssel für localStorage

const SongList = {
    allSongs: [], // Speichert alle initial geladenen Songs
    allSongs: [],
    isLoading: true,

    // Helferfunktion zum Laden der Favoriten-IDs aus localStorage
    _loadFavoriteIdsFromStorage: function() {
        try {
            const storedFavorites = localStorage.getItem(FAVORITES_STORAGE_KEY);
            if (storedFavorites) {
                // Parse den JSON-String und gib ein Set für schnelle Lookups zurück
                return new Set(JSON.parse(storedFavorites));
            }
        } catch (e) {
            console.error("Fehler beim Laden der Favoriten aus localStorage:", e);
            // Bei Fehlern oder wenn nichts gespeichert ist, leeres Set zurückgeben
        }
        return new Set();
    },

    // Helferfunktion zum Speichern der Favoriten-IDs in localStorage
    _saveFavoriteIdsToStorage: function(favoriteIdsSet) {
        try {
            // Konvertiere das Set in ein Array und dann in einen JSON-String
            localStorage.setItem(FAVORITES_STORAGE_KEY, JSON.stringify(Array.from(favoriteIdsSet)));
        } catch (e) {
            console.error("Fehler beim Speichern der Favoriten in localStorage:", e);
        }
    },

    loadAllSongs: function() {
        this.isLoading = true;
        // Simulierter API-Call
        // Lade zuerst die Favoriten-IDs aus dem Speicher
        const favoriteIdsFromStorage = this._loadFavoriteIdsFromStorage();

        // Simulierter API-Call (lädt Songs aus mockData)
        new Promise(resolve => setTimeout(() => resolve(initialSongs), 200))
            .then(data => {
                this.allSongs = data.map(s => ({ ...s, isFavorite: !!s.isFavorite }));
                // Mappe die geladenen Songs und setze 'isFavorite' basierend auf den gespeicherten IDs
                this.allSongs = data.map(songFromMock => ({
                    ...songFromMock, // Alle Eigenschaften des Songs aus mockData
                    isFavorite: favoriteIdsFromStorage.has(songFromMock.id) // Überschreibe/Setze isFavorite
                }));
                this.isLoading = false;
                m.redraw();
            }).catch(error => {
                console.error("Fehler beim Laden der Songs:", error);
                console.error("Fehler beim Laden der initialen Songdaten:", error);
                this.isLoading = false;
                m.redraw();
            });
@@ -26,6 +60,8 @@ const SongList = {
    },

    getProcessedSongsForView: function(viewFilter) {
        // Diese Funktion bleibt im Kern gleich, sie operiert auf this.allSongs,
        // welches jetzt den korrekten isFavorite-Status hat.
        let songsToProcess = [];
        let viewSpecificMessage = null;

@@ -38,8 +74,7 @@ const SongList = {
                    }
                    break;
                case 'popular':
                    // Hier könnten später Songs von 'https://example.com/api/songs/popular' geladen werden
                    songsToProcess = []; // Fürs Erste leer lassen
                    songsToProcess = [];
                    viewSpecificMessage = "Die Liste der beliebten Songs ist bald verfügbar!";
                    break;
                case 'all':
@@ -54,7 +89,6 @@ const SongList = {
             viewSpecificMessage = "Keine Songs geladen oder Bibliothek ist leer.";
        }


        const groups = {};
        songsToProcess.forEach(song => {
            groups[song.artist] = groups[song.artist] || [];
@@ -62,7 +96,6 @@ const SongList = {
        });

        const sortedArtistNames = Object.keys(groups).sort((a, b) => a.localeCompare(b));

        const finalGroupedSongs = {};
        sortedArtistNames.forEach(artist => {
            finalGroupedSongs[artist] = groups[artist].sort((a, b) => a.title.localeCompare(b.title));
@@ -74,14 +107,25 @@ const SongList = {
    toggleFavorite: function(songId) {
        const song = this.allSongs.find(s => s.id === songId);
        if (song) {
            song.isFavorite = !song.isFavorite;
            // API Call: m.request({ method: "PUT", url: `https://example.com/api/songs/${song.id}/favorite`, body: { isFavorite: song.isFavorite }})
            // Mithril's automatisches Redraw nach dem Event-Handler aktualisiert die Ansicht.
            // Wenn die 'Favoriten'-Ansicht aktiv ist, wird die Liste korrekt neu gerendert.
            song.isFavorite = !song.isFavorite; // In-Memory Status aktualisieren

            // Aktualisiere die Liste der Favoriten-IDs im localStorage
            const currentFavoriteIds = new Set();
            this.allSongs.forEach(s => {
                if (s.isFavorite) {
                    currentFavoriteIds.add(s.id);
                }
            });
            this._saveFavoriteIdsToStorage(currentFavoriteIds);

            // Der vorherige API Call Platzhalter wird hier nicht mehr benötigt,
            // da die Speicherung nun clientseitig erfolgt.
            // m.redraw() wird von Mithril nach dem Klick-Event automatisch ausgelöst.
        }
    },

    view: function(vnode) {
        // Die View-Logik bleibt unverändert
        const { currentView } = vnode.attrs;

        if (this.isLoading) {
@@ -91,18 +135,18 @@ const SongList = {
        const { groupedSongs, message, hasContent } = this.getProcessedSongsForView(currentView);
        const artists = Object.keys(groupedSongs);

        if (message && !hasContent) { // Zeige Nachricht nur, wenn keine Songs für die aktuelle Ansicht da sind
        if (message && !hasContent) {
            return m("div.song-list-outer-container", m("p.view-message", message));
        }

        return m("div.song-list-outer-container",
            artists.map(artist =>
                m("div.artist-group", { key: `${currentView}-${artist}` }, [ // Key um Eindeutigkeit bei View-Wechsel zu erhöhen
                m("div.artist-group", { key: `${currentView}-${artist}` }, [
                    m("h2.artist-name", artist),
                    groupedSongs[artist].map(song =>
                    groupedSongs[artist].map(s => // Geändert von 'song' zu 's' um Namenskonflikt zu vermeiden
                        m(SongItem, {
                            key: song.id,
                            song: song,
                            key: s.id,
                            song: s,
                            onToggleFavorite: this.toggleFavorite.bind(this)
                        })
                    )