fix(ui): derive Library Type dropdowns from library_types — audiobooks was missing
The audiobook library type shipped backend-complete (LibraryTypeAudiobooks constant, allowed extensions, MIME map, schema seed row, GET /api/libraries/types) but neither create-library form offered it: the admin Create Library modal and the setup wizard's add-library step both hardcoded their <option> lists to the original three types (ebooks/comics/manga), so an audiobook library could not be created from the UI. Both dropdowns now render from the same DB-backed library_types table the create API resolves types against — a future type appears without template edits (the exact drift the rendering-ownership rule warns about): - templates/utils.go: OrderedLibraryTypes pins the historical dropdown order (ebooks, comics, manga, audiobooks) and appends unknown future types by name; LibraryTypeDisplayName renders labels with a capitalize-the-name fallback for types added without a label - templates/admin_library.templ: new LibraryTypeOptions fragment used by the modal's select; AdminLibrary takes the types list - templates/setup.templ: Setup takes the types list, same fragment - internal/router/helpers.go: libraryTypeOptionsFor fetches via LibraryService.GetLibraryTypes, falling back to the built-in type names on DB failure so the forms stay usable (CreateLibrary still resolves the name against the DB, so the fallback is never authoritative) - internal/router/frontend.go, internal/router/setup.go: pass the list into the two pages Tests: - templates/utils_test.go: pure unit tests for ordering + labels - cmd/server/tests/admin_library_page_test.go: SSR regression — /admin/library must offer all four library types, audiobooks exactly once (options come from the DB loop, not a hardcode)
This commit is contained in:
1 parent
327fc2c0f9
commit
9e831085ef
10 files changed
+593
-346
No files matched your search
@@ -409,3 +409,61 @@ func badgeLabel(badge string) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Library type dropdown helpers. The option list comes from the DB-backed
|
||||
// library_types table (the same source the create API resolves types
|
||||
// against), so a new type appears in the UI without template edits.
|
||||
|
||||
// libraryTypeDisplayOrder pins the long-standing library types to their
|
||||
// historical dropdown order.
|
||||
var libraryTypeDisplayOrder = map[string]int{
|
||||
"ebooks": 0,
|
||||
"comics": 1,
|
||||
"manga": 2,
|
||||
"audiobooks": 3,
|
||||
}
|
||||
|
||||
const libraryTypeRankUnknown = 1 << 30
|
||||
|
||||
func libraryTypeRank(name string) int {
|
||||
if rank, ok := libraryTypeDisplayOrder[name]; ok {
|
||||
return rank
|
||||
}
|
||||
return libraryTypeRankUnknown
|
||||
}
|
||||
|
||||
// OrderedLibraryTypes sorts library types for a create-library dropdown:
|
||||
// the original types keep their UI order, any newer types follow by name.
|
||||
func OrderedLibraryTypes(types []database.LibraryTypes) []database.LibraryTypes {
|
||||
sorted := make([]database.LibraryTypes, len(types))
|
||||
copy(sorted, types)
|
||||
sort.SliceStable(sorted, func(i, j int) bool {
|
||||
ri, rj := libraryTypeRank(sorted[i].Name), libraryTypeRank(sorted[j].Name)
|
||||
if ri != rj {
|
||||
return ri < rj
|
||||
}
|
||||
return sorted[i].Name < sorted[j].Name
|
||||
})
|
||||
return sorted
|
||||
}
|
||||
|
||||
// LibraryTypeDisplayName renders a library_types.name for the UI
|
||||
// ("audiobooks" → "Audiobooks"); unknown names just get their first letter
|
||||
// capitalized so a future type still reads sensibly.
|
||||
func LibraryTypeDisplayName(name string) string {
|
||||
switch name {
|
||||
case "ebooks":
|
||||
return "Ebooks"
|
||||
case "comics":
|
||||
return "Comics"
|
||||
case "manga":
|
||||
return "Manga"
|
||||
case "audiobooks":
|
||||
return "Audiobooks"
|
||||
}
|
||||
if name == "" {
|
||||
return name
|
||||
}
|
||||
r := []rune(name)
|
||||
return strings.ToUpper(string(r[0])) + string(r[1:])
|
||||
}
|
||||
Reference in new issue
Block a user