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

feat: Initial frontend structure for song list (Mithril.js)

parents
Loading
Loading
Loading
Loading

frontend/SearchBar.mjs

0 → 100644
+18 −0
Original line number Diff line number Diff line
// SearchBar.mjs
const SearchBar = {
    view: function() {
        return m("div.search-bar-container", [
            m("input[type=search].search-input", { // type=search für bessere mobile Tastaturen
                placeholder: "Songs oder Interpreten suchen...",
                oninput: (e) => {
                    // Hier kommt später deine Suchlogik rein.
                    // console.log("Suchbegriff:", e.target.value);
                    // Für jetzt: Mache nichts.
                }
            })
        ]);
    }
};

export default SearchBar;
 No newline at end of file

frontend/SongItem.mjs

0 → 100644
+28 −0
Original line number Diff line number Diff line
// SongItem.mjs
const SongItem = {
    view: function(vnode) {
        const { song, onToggleFavorite } = vnode.attrs;
        const heartIconName = song.isFavorite ? "favorite" : "favorite_border";

        return m("div.song-item", { key: song.id }, [
            m("div.song-info", [
                m("div.song-title", song.title),
                m("div.song-artist", song.artist)
            ]),
            m("div.song-actions", [
                m("span.material-symbols-outlined.heart-icon", {
                      class: song.isFavorite ? 'is-favorite' : '',
                      onclick: () => onToggleFavorite(song.id),
                      title: song.isFavorite ? 'Als Favorit entfernen' : 'Als Favorit markieren'
                  },
                  // Material Symbols verwenden den Textinhalt, um das Icon zu bestimmen.
                  // Die Klasse .is-favorite steuert die FILL-Eigenschaft via CSS.
                  heartIconName
                )
            ])
        ]);
    }
};

export default SongItem;
 No newline at end of file

frontend/SongList.mjs

0 → 100644
+101 −0
Original line number Diff line number Diff line
// SongList.mjs
import SongItem from './SongItem.mjs';
import initialSongs from './mockData.mjs'; // Deine Mock-Daten

const SongList = {
    songs: [],
    groupedAndSortedSongs: {},
    isLoading: true,

    loadSongs: function() {
        this.isLoading = true;
        // Simuliere einen API-Call
        // In echt: m.request({ url: "https://example.com/api/songs" }).then(...)
        new Promise(resolve => setTimeout(() => resolve(initialSongs), 200)) // Kurze Verzögerung für Lade-Effekt
            .then(data => {
                this.songs = data.map(s => ({ ...s, isFavorite: !!s.isFavorite })); // Stelle sicher, dass isFavorite existiert
                this.processSongs();
                this.isLoading = false;
                m.redraw(); // Wichtig nach asynchronen Operationen außerhalb Mithrils Lebenszyklus
            }).catch(error => {
                console.error("Fehler beim Laden der Songs:", error);
                this.isLoading = false;
                // Hier könntest du eine Fehlermeldung anzeigen
                m.redraw();
            });
    },

    oninit: function() {
        this.loadSongs();
    },

    processSongs: function() {
        const groups = {};
        this.songs.forEach(song => {
            groups[song.artist] = groups[song.artist] || [];
            groups[song.artist].push(song);
        });

        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));
        });
        this.groupedAndSortedSongs = finalGroupedSongs;
    },

    toggleFavorite: function(songId) {
        const song = this.songs.find(s => s.id === songId);
        if (song) {
            song.isFavorite = !song.isFavorite;
            // Hier würdest du den API Call machen:
            // m.request({
            //     method: "PUT",
            //     url: `https://example.com/api/songs/${song.id}/favorite`,
            //     body: { isFavorite: song.isFavorite }
            // }).then(() => {
            //     console.log(`Favoritenstatus für ${song.title} geändert.`);
            // }).catch(e => {
            //     console.error("Fehler beim Ändern des Favoritenstatus:", e);
            //     song.isFavorite = !song.isFavorite; // Rollback bei Fehler
            //     m.redraw(); // UI aktualisieren
            // });

            // Für dieses Frontend-Beispiel reicht das direkte Ändern und Neuzeichnen (Mithril macht das bei Event-Handlern meist automatisch)
            // Da `groupedAndSortedSongs` von `this.songs` abhängt, muss es nicht neu prozessiert werden,
            // solange die Reihenfolge/Gruppierung sich nicht ändert, nur das Icon.
            // Ein m.redraw() wird implizit durch den Klick-Handler ausgelöst.
        }
    },

    view: function() {
        if (this.isLoading) {
            return m("div.song-list-outer-container", m("div.loading-placeholder", "Lade Songs..."));
        }

        const artists = Object.keys(this.groupedAndSortedSongs);

        if (artists.length === 0) {
            return m("div.song-list-outer-container", m("p", "Keine Songs verfügbar."));
        }

        return m("div.song-list-outer-container",
            artists.map(artist =>
                m("div.artist-group", { key: artist }, [
                    m("h2.artist-name", artist),
                    this.groupedAndSortedSongs[artist].map(song =>
                        m(SongItem, {
                            key: song.id,
                            song: song,
                            onToggleFavorite: this.toggleFavorite.bind(this)
                        })
                    )
                ])
            )
        );
    }
};

export default SongList;
 No newline at end of file

frontend/app.mjs

0 → 100644
+20 −0
Original line number Diff line number Diff line
// app.mjs
import SearchBar from './SearchBar.mjs';
import SongList from './SongList.mjs';

const AppLayout = {
    view: function() {
        return m("div.app-layout", [
            m(SearchBar),
            m(SongList)
        ]);
    }
};

const appRoot = document.getElementById("app");
if (appRoot) {
    m.mount(appRoot, AppLayout);
} else {
    console.error("Root-Element #app nicht gefunden!");
}
 No newline at end of file

frontend/index.html

0 → 100644
+20 −0
Original line number Diff line number Diff line
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Meine Songs</title>
    <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet">
    <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet" />
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div id="app">
        <div class="loading-placeholder">Lade App...</div>
    </div>

    <script src="https://unpkg.com/mithril/mithril.js"></script>
    <script type="module" src="app.mjs"></script>
</body>
</html>
 No newline at end of file