Compare commits

...

10 Commits

Author SHA1 Message Date
0373572eb7
Implement favorites page 2025-11-13 19:55:40 +01:00
6773d6d9bc
Add pagination elements 2025-11-13 19:48:09 +01:00
7c6ba261f8
Add SongList 2025-11-13 19:47:51 +01:00
0b69162d0b
Add search bar 2025-11-13 19:47:34 +01:00
740cee3d44
Add header (featuring SearchBar and TabBar) 2025-11-13 19:47:22 +01:00
be55bb91dc
Add index.html
With fonts retrieved from Google Fonts, which is not ideal, but we can inlne those
2025-11-13 19:47:07 +01:00
b3cefa32b7
Add tab bar 2025-11-13 19:42:27 +01:00
30b9bdf37c
Add artist <section> 2025-11-13 19:42:07 +01:00
008ec35176
Add page for all songs 2025-11-13 19:41:24 +01:00
fda41d1bdd
Add base page (header and main) 2025-11-13 19:39:38 +01:00
10 changed files with 175 additions and 0 deletions

View File

@ -0,0 +1,23 @@
import Base from "./Base.js"
import SongList from "../pieces/SongList.js"
import Song from "../../model/Song.js"
import search from "../../model/search.js"
export default {
oncreate() {
document.title = "Alle Songs · Karaokatalog"
},
oninit() {
Song.load()
},
view() {
const songs = search.apply(Song.all)
const message = !songs ? "Lade Songs..." : songs.length === 0 ? "Keine Songs gefunden." : null
return m(
Base,
message && m("p", message),
songs && m(SongList, { songs })
)
},
}

View File

@ -0,0 +1,7 @@
import Header from "../pieces/Header.js"
export default {
view(vnode) {
return [m(Header), m("main", vnode.children)]
},
}

View File

@ -0,0 +1,41 @@
import Base from "./Base.js"
import SongList from "../pieces/SongList.js"
import Song from "../../model/Song.js"
import search from "../../model/search.js"
import ClearSearchLink from "../pieces/ClearSearchLink.js"
export default {
oncreate() {
document.title = "Favoriten · Karaokatalog"
},
oninit() {
Song.load()
},
view() {
const allFavorites = Song.all?.filter(song => song.favorite)
const favoritesToShow = search.apply(allFavorites)
let message = null
if(!allFavorites) {
message = "Lade Songs..."
} else if(allFavorites.length === 0) {
message = "Du hast noch keine Favoriten markiert."
} else if(favoritesToShow.length === 0) {
message = [
`Deine Suche ergab keine Treffer unter deinen ${allFavorites.length} Favoriten. `,
m(ClearSearchLink, { totalFavoriteCount: allFavorites.length}),
]
} else if(allFavorites.length > favoritesToShow.length) {
message = [
`Deine Suche zeigt ${favoritesToShow.length} von deinen insgesamt ${allFavorites.length} Favoriten. `,
m(ClearSearchLink, { totalFavoriteCount: allFavorites.length}),
]
}
return m(
Base,
message && m("p", message),
favoritesToShow && m(SongList, { songs: favoritesToShow }),
)
},
}

View File

@ -0,0 +1,14 @@
import Song from "./Song.js"
export default {
view: function (vnode) {
const { artist, songs } = vnode.attrs
return m(
"section.artist",
[
m("h2", artist),
songs.map(song => m(Song, { song })),
],
)
},
}

View File

@ -0,0 +1,8 @@
import SearchBar from "./SearchBar.js"
import TabBar from "./TabBar.js"
export default {
view() {
return m("header", [m(SearchBar), m(TabBar)])
},
}

View File

@ -0,0 +1,16 @@
import PaginationControls from "./PaginationControls.js"
const ELEMENTS_PER_PAGE = 25
export default {
view(vnode) {
const elements = vnode.attrs.elements.toArray()
const totalPages = Math.ceil(elements.length / ELEMENTS_PER_PAGE)
const startIndex = (this.currentPageIndex || 0) * ELEMENTS_PER_PAGE
return [
elements.slice(startIndex, startIndex + ELEMENTS_PER_PAGE),
m(PaginationControls, { currentPage: (this.currentPageIndex || 0) + 1, totalPages, onPageChange: i => this.currentPageIndex = i - 1 })
]
}
}

View File

@ -0,0 +1,15 @@
import search from "../../model/search.js"
export default {
view: function () {
return m("search", [
m("input[type=search]", {
value: search.query,
placeholder: "Songs oder Interpreten suchen...",
oninput: e => {
search.query = e.target.value
},
}),
])
},
}

View File

@ -0,0 +1,11 @@
import Artist from "./Artist.js"
import Pagination from "./Pagination.js"
export default {
view: function (vnode) {
const sortedSongs = vnode.attrs.songs.toSorted((a, b) => a.artist.localeCompare(b.artist) || a.title.localeCompare(b.title))
const songsByArtist = Map.groupBy(sortedSongs, song => song.artist)
return m(Pagination, { elements: songsByArtist.entries().map(([artist, songs]) => m(Artist, { artist, songs })) })
},
}

View File

@ -0,0 +1,16 @@
import { Tab } from "./Tab.js"
export default {
view() {
return m("nav", [
m(Tab, {
href: "/all",
label: "Alle",
}),
m(Tab, {
href: "/favorites",
label: "Favoriten",
}),
])
},
}

View File

@ -0,0 +1,24 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Karaokatalog</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:opsz,wght,FILL,GRAD@24,400,0..1,0"
rel="stylesheet"
/>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<main>
<p>Lade Karaokatalog-App...</p>
</main>
<script type="module" src="karaokatalog.js"></script>
</body>
</html>