Loading frontend/PaginationControls.mjs +88 −59 Original line number Diff line number Diff line // frontend/PaginationControls.mjs const PaginationControls = { view: function (vnode) { const { currentPage, totalPages, onPageChange } = vnode.attrs; const { currentPage, totalPages, onPageChange } = vnode.attrs if (totalPages <= 1) { return null; // Keine Steuerelemente anzeigen, wenn nicht benötigt return null // Keine Steuerelemente anzeigen, wenn nicht benötigt } const pages = []; const delta = 2; // Anzahl der Seiten, die um die aktuelle Seite herum angezeigt werden const pages = [] const delta = 2 // Anzahl der Seiten, die um die aktuelle Seite herum angezeigt werden // Hilfsfunktion zum Hinzufügen einer Seitenzahl const addPage = (pageNumber) => { pages.push(m("span.page-number", { class: pageNumber === currentPage ? 'active' : 'clickable', onclick: () => pageNumber !== currentPage ? onPageChange(pageNumber) : null, const addPage = pageNumber => { pages.push( m( "span.page-number", { class: pageNumber === currentPage ? "active" : "clickable", onclick: () => pageNumber !== currentPage ? onPageChange(pageNumber) : null, role: "button", tabindex: 0, // Für Tastaturnavigation onkeypress: (e) => { // Für Tastaturnavigation (Enter/Space) if ((e.key === 'Enter' || e.key === ' ') && pageNumber !== currentPage) { e.preventDefault(); onPageChange(pageNumber); onkeypress: e => { // Für Tastaturnavigation (Enter/Space) if ( (e.key === "Enter" || e.key === " ") && pageNumber !== currentPage ) { e.preventDefault() onPageChange(pageNumber) } }, "aria-label": `Gehe zu Seite ${pageNumber}`, "aria-current": pageNumber === currentPage ? "page" : undefined }, pageNumber)); }; "aria-current": pageNumber === currentPage ? "page" : undefined, }, pageNumber, ), ) } // "Zurück"-Button const prevButton = m("button.pagination-button", { const prevButton = m( "button.pagination-button", { onclick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Vorherige Seite" }, "Zurück"); "aria-label": "Vorherige Seite", }, "Zurück", ) // "Weiter"-Button const nextButton = m("button.pagination-button", { const nextButton = m( "button.pagination-button", { onclick: () => onPageChange(currentPage + 1), disabled: currentPage === totalPages, "aria-label": "Nächste Seite" }, "Weiter"); "aria-label": "Nächste Seite", }, "Weiter", ) // Logik zur Seitenzahlanzeige if (totalPages <= 7) { // Alle Seiten anzeigen, wenn es 7 oder weniger sind if (totalPages <= 7) { // Alle Seiten anzeigen, wenn es 7 oder weniger sind for (let i = 1; i <= totalPages; i++) { addPage(i); addPage(i) } } else { addPage(1); // Immer die erste Seite anzeigen addPage(1) // Immer die erste Seite anzeigen if (currentPage > delta + 2) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Anfang entfernt ist pages.push(m("span.dots", "...")); if (currentPage > delta + 2) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Anfang entfernt ist pages.push(m("span.dots", "...")) } // Bereich der Seiten um die aktuelle Seite bestimmen let start = Math.max(2, currentPage - delta); let end = Math.min(totalPages - 1, currentPage + delta); if (currentPage <= delta + 1) { // Aktuelle Seite ist nahe am Anfang start = 2; end = Math.min(totalPages - 1, 2 * delta + 1); } else if (currentPage >= totalPages - delta) { // Aktuelle Seite ist nahe am Ende start = Math.max(2, totalPages - (2 * delta)); end = totalPages - 1; let start = Math.max(2, currentPage - delta) let end = Math.min(totalPages - 1, currentPage + delta) if (currentPage <= delta + 1) { // Aktuelle Seite ist nahe am Anfang start = 2 end = Math.min(totalPages - 1, 2 * delta + 1) } else if (currentPage >= totalPages - delta) { // Aktuelle Seite ist nahe am Ende start = Math.max(2, totalPages - 2 * delta) end = totalPages - 1 } // Korrektur für den Fall, dass start und end sich überschneiden oder ungültig werden if (start > end && currentPage < totalPages / 2) { // Wenn start > end und wir sind eher am Anfang end = start; // Zeige zumindest die Startseite } else if (start > end && currentPage > totalPages / 2) { // Wenn start > end und wir sind eher am Ende start = end; // Zeige zumindest die Endseite if (start > end && currentPage < totalPages / 2) { // Wenn start > end und wir sind eher am Anfang end = start // Zeige zumindest die Startseite } else if (start > end && currentPage > totalPages / 2) { // Wenn start > end und wir sind eher am Ende start = end // Zeige zumindest die Endseite } for (let i = start; i <= end; i++) { if (i > 0) addPage(i); // Sicherstellen, dass i positiv ist if (i > 0) addPage(i) // Sicherstellen, dass i positiv ist } if (currentPage < totalPages - delta - 1 && end < totalPages -1) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Ende entfernt ist pages.push(m("span.dots", "...")); if (currentPage < totalPages - delta - 1 && end < totalPages - 1) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Ende entfernt ist pages.push(m("span.dots", "...")) } if (totalPages > 1) addPage(totalPages); // Immer die letzte Seite anzeigen (wenn nicht 1) if (totalPages > 1) addPage(totalPages) // Immer die letzte Seite anzeigen (wenn nicht 1) } return m("div.pagination-controls", [prevButton, ...pages, nextButton]); return m("div.pagination-controls", [prevButton, ...pages, nextButton]) }, } }; export default PaginationControls; export default PaginationControls frontend/SearchBar.mjs +10 −10 Original line number Diff line number Diff line Loading @@ -2,22 +2,22 @@ const SearchBar = { view: function (vnode) { // Empfange den aktuellen Suchbegriff und den Callback als Attribute const { searchTerm, onSearchTermChange } = vnode.attrs; const { searchTerm, onSearchTermChange } = vnode.attrs return m("div.search-bar-container", [ m("input[type=search].search-input", { placeholder: "Songs oder Interpreten suchen...", value: searchTerm, // Binde den Wert des Input-Feldes an den searchTerm-State oninput: (e) => { onSearchTermChange(e.target.value); // Rufe den Callback bei jeder Eingabe auf oninput: e => { onSearchTermChange(e.target.value) // Rufe den Callback bei jeder Eingabe auf }, // Optional: onsearch für explizite Suchaktionen (z.B. bei Enter oder Klick auf X) // onsearch: (e) => { // if (e.target.value === '') { onSearchTermChange(''); } // } }) ]); }), ]) }, } }; export default SearchBar; No newline at end of file export default SearchBar frontend/SongItem.mjs +20 −16 Original line number Diff line number Diff line // SongItem.mjs const SongItem = { view: function (vnode) { const { song, onToggleFavorite } = vnode.attrs; const { song, onToggleFavorite } = vnode.attrs // Immer den Basis-Icon-Namen verwenden. Die CSS-Klasse steuert das FILL. const heartIconName = "favorite"; const heartIconName = "favorite" 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-artist", song.artist), ]), m("div.song-actions", [ m("span.material-symbols-outlined.heart-icon", { class: song.isFavorite ? 'is-favorite' : '', // Diese Klasse toggelt FILL via CSS m( "span.material-symbols-outlined.heart-icon", { class: song.isFavorite ? "is-favorite" : "", // Diese Klasse toggelt FILL via CSS onclick: () => onToggleFavorite(song.id), title: song.isFavorite ? 'Als Favorit entfernen' : 'Als Favorit markieren' title: song.isFavorite ? "Als Favorit entfernen" : "Als Favorit markieren", }, heartIconName // Ist jetzt immer "favorite" ) heartIconName, // Ist jetzt immer "favorite" ), ]), ]) ]); }, } }; export default SongItem; No newline at end of file export default SongItem frontend/SongList.mjs +235 −157 File changed.Preview size limit exceeded, changes collapsed. Show changes frontend/TabBar.mjs +26 −18 Original line number Diff line number Diff line // TabBar.mjs const TabBar = { view: function (vnode) { const { currentView, onViewChange } = vnode.attrs; const { currentView, onViewChange } = vnode.attrs const tabs = [ { id: 'all', label: 'Alle' }, { id: 'favorites', label: 'Favoriten' }, { id: "all", label: "Alle" }, { id: "favorites", label: "Favoriten" }, // { id: 'popular', label: 'Beliebt' } // 'Beliebt' als Option ]; ] return m("div.tab-bar-container", return m( "div.tab-bar-container", tabs.map(tab => { return m("div.tab-item", { class: currentView === tab.id ? 'active' : '', return m( "div.tab-item", { class: currentView === tab.id ? "active" : "", onclick: () => onViewChange(tab.id), role: "button", // Für Barrierefreiheit tabindex: 0, // Für Tastaturnavigation onkeypress: (e) => { if (e.key === 'Enter' || e.key === ' ') onViewChange(tab.id); } }, tab.label); }) ); onkeypress: e => { if (e.key === "Enter" || e.key === " ") onViewChange(tab.id) }, }, tab.label, ) }), ) }, } }; export default TabBar; No newline at end of file export default TabBar Loading
frontend/PaginationControls.mjs +88 −59 Original line number Diff line number Diff line // frontend/PaginationControls.mjs const PaginationControls = { view: function (vnode) { const { currentPage, totalPages, onPageChange } = vnode.attrs; const { currentPage, totalPages, onPageChange } = vnode.attrs if (totalPages <= 1) { return null; // Keine Steuerelemente anzeigen, wenn nicht benötigt return null // Keine Steuerelemente anzeigen, wenn nicht benötigt } const pages = []; const delta = 2; // Anzahl der Seiten, die um die aktuelle Seite herum angezeigt werden const pages = [] const delta = 2 // Anzahl der Seiten, die um die aktuelle Seite herum angezeigt werden // Hilfsfunktion zum Hinzufügen einer Seitenzahl const addPage = (pageNumber) => { pages.push(m("span.page-number", { class: pageNumber === currentPage ? 'active' : 'clickable', onclick: () => pageNumber !== currentPage ? onPageChange(pageNumber) : null, const addPage = pageNumber => { pages.push( m( "span.page-number", { class: pageNumber === currentPage ? "active" : "clickable", onclick: () => pageNumber !== currentPage ? onPageChange(pageNumber) : null, role: "button", tabindex: 0, // Für Tastaturnavigation onkeypress: (e) => { // Für Tastaturnavigation (Enter/Space) if ((e.key === 'Enter' || e.key === ' ') && pageNumber !== currentPage) { e.preventDefault(); onPageChange(pageNumber); onkeypress: e => { // Für Tastaturnavigation (Enter/Space) if ( (e.key === "Enter" || e.key === " ") && pageNumber !== currentPage ) { e.preventDefault() onPageChange(pageNumber) } }, "aria-label": `Gehe zu Seite ${pageNumber}`, "aria-current": pageNumber === currentPage ? "page" : undefined }, pageNumber)); }; "aria-current": pageNumber === currentPage ? "page" : undefined, }, pageNumber, ), ) } // "Zurück"-Button const prevButton = m("button.pagination-button", { const prevButton = m( "button.pagination-button", { onclick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Vorherige Seite" }, "Zurück"); "aria-label": "Vorherige Seite", }, "Zurück", ) // "Weiter"-Button const nextButton = m("button.pagination-button", { const nextButton = m( "button.pagination-button", { onclick: () => onPageChange(currentPage + 1), disabled: currentPage === totalPages, "aria-label": "Nächste Seite" }, "Weiter"); "aria-label": "Nächste Seite", }, "Weiter", ) // Logik zur Seitenzahlanzeige if (totalPages <= 7) { // Alle Seiten anzeigen, wenn es 7 oder weniger sind if (totalPages <= 7) { // Alle Seiten anzeigen, wenn es 7 oder weniger sind for (let i = 1; i <= totalPages; i++) { addPage(i); addPage(i) } } else { addPage(1); // Immer die erste Seite anzeigen addPage(1) // Immer die erste Seite anzeigen if (currentPage > delta + 2) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Anfang entfernt ist pages.push(m("span.dots", "...")); if (currentPage > delta + 2) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Anfang entfernt ist pages.push(m("span.dots", "...")) } // Bereich der Seiten um die aktuelle Seite bestimmen let start = Math.max(2, currentPage - delta); let end = Math.min(totalPages - 1, currentPage + delta); if (currentPage <= delta + 1) { // Aktuelle Seite ist nahe am Anfang start = 2; end = Math.min(totalPages - 1, 2 * delta + 1); } else if (currentPage >= totalPages - delta) { // Aktuelle Seite ist nahe am Ende start = Math.max(2, totalPages - (2 * delta)); end = totalPages - 1; let start = Math.max(2, currentPage - delta) let end = Math.min(totalPages - 1, currentPage + delta) if (currentPage <= delta + 1) { // Aktuelle Seite ist nahe am Anfang start = 2 end = Math.min(totalPages - 1, 2 * delta + 1) } else if (currentPage >= totalPages - delta) { // Aktuelle Seite ist nahe am Ende start = Math.max(2, totalPages - 2 * delta) end = totalPages - 1 } // Korrektur für den Fall, dass start und end sich überschneiden oder ungültig werden if (start > end && currentPage < totalPages / 2) { // Wenn start > end und wir sind eher am Anfang end = start; // Zeige zumindest die Startseite } else if (start > end && currentPage > totalPages / 2) { // Wenn start > end und wir sind eher am Ende start = end; // Zeige zumindest die Endseite if (start > end && currentPage < totalPages / 2) { // Wenn start > end und wir sind eher am Anfang end = start // Zeige zumindest die Startseite } else if (start > end && currentPage > totalPages / 2) { // Wenn start > end und wir sind eher am Ende start = end // Zeige zumindest die Endseite } for (let i = start; i <= end; i++) { if (i > 0) addPage(i); // Sicherstellen, dass i positiv ist if (i > 0) addPage(i) // Sicherstellen, dass i positiv ist } if (currentPage < totalPages - delta - 1 && end < totalPages -1) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Ende entfernt ist pages.push(m("span.dots", "...")); if (currentPage < totalPages - delta - 1 && end < totalPages - 1) { // Auslassungspunkte anzeigen, wenn die aktuelle Seite weit vom Ende entfernt ist pages.push(m("span.dots", "...")) } if (totalPages > 1) addPage(totalPages); // Immer die letzte Seite anzeigen (wenn nicht 1) if (totalPages > 1) addPage(totalPages) // Immer die letzte Seite anzeigen (wenn nicht 1) } return m("div.pagination-controls", [prevButton, ...pages, nextButton]); return m("div.pagination-controls", [prevButton, ...pages, nextButton]) }, } }; export default PaginationControls; export default PaginationControls
frontend/SearchBar.mjs +10 −10 Original line number Diff line number Diff line Loading @@ -2,22 +2,22 @@ const SearchBar = { view: function (vnode) { // Empfange den aktuellen Suchbegriff und den Callback als Attribute const { searchTerm, onSearchTermChange } = vnode.attrs; const { searchTerm, onSearchTermChange } = vnode.attrs return m("div.search-bar-container", [ m("input[type=search].search-input", { placeholder: "Songs oder Interpreten suchen...", value: searchTerm, // Binde den Wert des Input-Feldes an den searchTerm-State oninput: (e) => { onSearchTermChange(e.target.value); // Rufe den Callback bei jeder Eingabe auf oninput: e => { onSearchTermChange(e.target.value) // Rufe den Callback bei jeder Eingabe auf }, // Optional: onsearch für explizite Suchaktionen (z.B. bei Enter oder Klick auf X) // onsearch: (e) => { // if (e.target.value === '') { onSearchTermChange(''); } // } }) ]); }), ]) }, } }; export default SearchBar; No newline at end of file export default SearchBar
frontend/SongItem.mjs +20 −16 Original line number Diff line number Diff line // SongItem.mjs const SongItem = { view: function (vnode) { const { song, onToggleFavorite } = vnode.attrs; const { song, onToggleFavorite } = vnode.attrs // Immer den Basis-Icon-Namen verwenden. Die CSS-Klasse steuert das FILL. const heartIconName = "favorite"; const heartIconName = "favorite" 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-artist", song.artist), ]), m("div.song-actions", [ m("span.material-symbols-outlined.heart-icon", { class: song.isFavorite ? 'is-favorite' : '', // Diese Klasse toggelt FILL via CSS m( "span.material-symbols-outlined.heart-icon", { class: song.isFavorite ? "is-favorite" : "", // Diese Klasse toggelt FILL via CSS onclick: () => onToggleFavorite(song.id), title: song.isFavorite ? 'Als Favorit entfernen' : 'Als Favorit markieren' title: song.isFavorite ? "Als Favorit entfernen" : "Als Favorit markieren", }, heartIconName // Ist jetzt immer "favorite" ) heartIconName, // Ist jetzt immer "favorite" ), ]), ]) ]); }, } }; export default SongItem; No newline at end of file export default SongItem
frontend/SongList.mjs +235 −157 File changed.Preview size limit exceeded, changes collapsed. Show changes
frontend/TabBar.mjs +26 −18 Original line number Diff line number Diff line // TabBar.mjs const TabBar = { view: function (vnode) { const { currentView, onViewChange } = vnode.attrs; const { currentView, onViewChange } = vnode.attrs const tabs = [ { id: 'all', label: 'Alle' }, { id: 'favorites', label: 'Favoriten' }, { id: "all", label: "Alle" }, { id: "favorites", label: "Favoriten" }, // { id: 'popular', label: 'Beliebt' } // 'Beliebt' als Option ]; ] return m("div.tab-bar-container", return m( "div.tab-bar-container", tabs.map(tab => { return m("div.tab-item", { class: currentView === tab.id ? 'active' : '', return m( "div.tab-item", { class: currentView === tab.id ? "active" : "", onclick: () => onViewChange(tab.id), role: "button", // Für Barrierefreiheit tabindex: 0, // Für Tastaturnavigation onkeypress: (e) => { if (e.key === 'Enter' || e.key === ' ') onViewChange(tab.id); } }, tab.label); }) ); onkeypress: e => { if (e.key === "Enter" || e.key === " ") onViewChange(tab.id) }, }, tab.label, ) }), ) }, } }; export default TabBar; No newline at end of file export default TabBar