Commit bf830420 authored by Jakob Moser's avatar Jakob Moser
Browse files

Use new model style, rework favorite handling



Co-Authored-By: default avatarCursor AI <ai@cursor.com>
parent 101670ea
Loading
Loading
Loading
Loading
+0 −2
Original line number Diff line number Diff line
@@ -9,7 +9,6 @@ const Artist = {
            currentView,
            searchTerm,
            currentPage,
            onToggleFavorite,
        } = vnode.attrs
        return m(
            "div.artist",
@@ -22,7 +21,6 @@ const Artist = {
                    m(Song, {
                        key: s.id,
                        song: s,
                        onToggleFavorite,
                    }),
                ),
            ],
+4 −4
Original line number Diff line number Diff line
// Song.mjs
const Song = {
    view: function (vnode) {
        const { song, onToggleFavorite } = vnode.attrs
        const { song } = vnode.attrs
        const heartIconName = "favorite"

        return m("div.song", { key: song.id }, [
@@ -13,9 +13,9 @@ const Song = {
                m(
                    "span.material-symbols-outlined.heart-icon",
                    {
                        class: song.isFavorite ? "is-favorite" : "",
                        onclick: () => onToggleFavorite(song.id),
                        title: song.isFavorite
                        class: song.favorite ? "is-favorite" : "",
                        onclick: () => song.toggleFavorite(),
                        title: song.favorite
                            ? "Als Favorit entfernen"
                            : "Als Favorit markieren",
                    },
+0 −1
Original line number Diff line number Diff line
@@ -97,7 +97,6 @@ const SongList = {
                        currentView,
                        query,
                        currentPage,
                        onToggleFavorite: model.toggleFavorite.bind(model),
                    }),
                ),
            )
+20 −13
Original line number Diff line number Diff line
export default class Song {
    constructor({ id, title, artist, isFavorite = false }) {
        this.id = id
        this.title = title
        this.artist = artist
        this.isFavorite = isFavorite
import { Base } from "./Base.mjs"
import { getFavoriteIds, addFavorite, removeFavorite } from "./favorites.js"

export default class Song extends Base {
    static async forceLoad() {
        return await m.request({ url: "/api/songs" })
    }

    get favorite() {
        return getFavoriteIds().has(this.id)
    }

    set favorite(isFavorite) {
        if (isFavorite) {
            addFavorite(this.id)
        } else {
            removeFavorite(this.id)
        }
    }

    static fromObject(obj) {
        return new Song({
            id: obj.id,
            title: obj.title,
            artist: obj.artist,
            isFavorite: obj.isFavorite ?? false,
        })
    toggleFavorite() {
        this.favorite = !this.favorite
    }
}
+15 −89
Original line number Diff line number Diff line
import Song from "./Song.js"

const FAVORITES_STORAGE_KEY = "songAppFavorites"
const API_URL_SONGS = "/api/songs"

const SongListModel = {
    allSongs: [],
    isLoading: true,
    currentPage: 1,
    itemsPerPage: 60, // Wie vom Benutzer angegeben
    totalPagesComputed: 1,

    _loadFavoriteIdsFromStorage: function () {
        try {
            const storedFavorites = localStorage.getItem(FAVORITES_STORAGE_KEY)
            if (storedFavorites) {
                return new Set(JSON.parse(storedFavorites))
            }
        } catch (e) {
            console.error(
                "Fehler beim Laden der Favoriten aus localStorage:",
                e,
            )
        }
        return new Set()
    },

    _saveFavoriteIdsToStorage: function (favoriteIdsSet) {
        try {
            localStorage.setItem(
                FAVORITES_STORAGE_KEY,
                JSON.stringify(Array.from(favoriteIdsSet)),
            )
        } catch (e) {
            console.error(
                "Fehler beim Speichern der Favoriten in localStorage:",
                e,
            )
        }
    },

    loadAllSongsFromServer: function () {
        this.isLoading = true
        this.currentPage = 1
        const favoriteIdsFromStorage = this._loadFavoriteIdsFromStorage()
        m.request({
            method: "GET",
            url: API_URL_SONGS,
        })
            .then(dataFromServer => {
                const songsArray = Array.isArray(dataFromServer)
                    ? dataFromServer
                    : []
                this.allSongs = songsArray.map(songFromServer => {
                    const song = Song.fromObject(songFromServer)
                    song.isFavorite = favoriteIdsFromStorage.has(song.id)
                    return song
                })
                this.isLoading = false
                m.redraw()
            })
            .catch(error => {
                console.error("Fehler beim Laden der Songs vom Server:", error)
                this.allSongs = []
                this.isLoading = false
                m.redraw()
            })
    },

    oninit: function () {
        this.loadAllSongsFromServer()
    oninit: async function () {
        await Song.load()
    },

    onbeforeupdate: function (vnode, old) {
        if (
            vnode.attrs.currentView !== old.attrs.currentView ||
            vnode.attrs.searchTerm !== old.attrs.searchTerm
            (vnode.attrs.query || "") !== (old.attrs.query || "")
        ) {
            this.currentPage = 1
        }
@@ -87,15 +25,15 @@ const SongListModel = {
        }
    },

    getProcessedSongsForView: function (viewFilter, searchTerm) {
    getProcessedSongsForView: function (viewFilter, query) {
        let initialSongsForView = []
        let messageFromTabFilter = null // Nachricht rein basierend auf Tab-Auswahl

        // 1. Songs basierend auf dem aktuellen Tab (View) filtern
        switch (viewFilter) {
            case "favorites":
                initialSongsForView = this.allSongs.filter(
                    song => song.isFavorite,
                initialSongsForView = (Song.all || []).filter(
                    song => song.favorite,
                )
                if (initialSongsForView.length === 0) {
                    // Überhaupt keine Favoriten
@@ -110,8 +48,8 @@ const SongListModel = {
                break
            case "all":
            default:
                initialSongsForView = this.allSongs
                if (initialSongsForView.length === 0 && !this.isLoading) {
                initialSongsForView = Song.all || []
                if (initialSongsForView.length === 0) {
                    messageFromTabFilter = "Keine Songs in der Bibliothek."
                }
                break
@@ -121,15 +59,15 @@ const SongListModel = {
        let composedMessage = null // Endgültige Nachricht für den Benutzer

        // 2. Suchbegriff anwenden und Nachrichten erstellen
        const trimmedSearchTerm = searchTerm ? searchTerm.trim() : ""
        if (trimmedSearchTerm !== "") {
            const lowerSearchTerm = trimmedSearchTerm.toLowerCase()
        const trimmedQuery = query ? query.trim() : ""
        if (trimmedQuery !== "") {
            const lowerQuery = trimmedQuery.toLowerCase()
            songsAfterSearch = initialSongsForView.filter(
                song =>
                    (song.title &&
                        song.title.toLowerCase().includes(lowerSearchTerm)) ||
                        song.title.toLowerCase().includes(lowerQuery)) ||
                    (song.artist &&
                        song.artist.toLowerCase().includes(lowerSearchTerm)),
                        song.artist.toLowerCase().includes(lowerQuery)),
            )

            if (viewFilter === "favorites") {
@@ -193,7 +131,7 @@ const SongListModel = {

        // 5. Endgültige Nachricht, falls nach Filterung/Suche gar keine Items da sind (über alle Seiten)
        //    und noch keine spezifischere Nachricht (z.B. Favoriten-Logik) gesetzt wurde.
        if (totalFilteredItems === 0 && !composedMessage && !this.isLoading) {
        if (totalFilteredItems === 0 && !composedMessage) {
            composedMessage = "Keine Songs entsprechen den aktuellen Kriterien."
        }

@@ -205,19 +143,7 @@ const SongListModel = {
            totalPages: totalPages,
            totalFilteredItems: totalFilteredItems,
        }
    },

    toggleFavorite: function (songId) {
        const song = this.allSongs.find(s => s.id === songId)
        if (song) {
            song.isFavorite = !song.isFavorite
            // Persist favorites
            const favoriteIds = this.allSongs
                .filter(s => s.isFavorite)
                .map(s => s.id)
            this._saveFavoriteIdsToStorage(new Set(favoriteIds))
    }
    },
}

export default SongListModel
Loading