-
+ {{range orderedGenres .GenresList $selectedGenreIDs}}
+ {{$genre := .Genre}}
+
{{end}}
diff --git a/templates/funcs.go b/templates/funcs.go
index fdea393..8507934 100644
--- a/templates/funcs.go
+++ b/templates/funcs.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"html/template"
+ "mal/internal/domain"
"net/url"
"os"
"slices"
@@ -12,6 +13,12 @@ import (
"time"
)
+type genreOption struct {
+ Genre domain.Genre
+ Selected bool
+ StartsInactive bool
+}
+
func dict(values ...any) (map[string]any, error) {
if len(values)%2 != 0 {
return nil, fmt.Errorf("dict expects even number of values, got %d", len(values))
@@ -160,6 +167,31 @@ func hasGenre(id int, genres []int) bool {
return slices.Contains(genres, id)
}
+func orderedGenreOptions(genres []domain.Genre, selectedGenres []int) []genreOption {
+ selected := make(map[int]struct{}, len(selectedGenres))
+ for _, genreID := range selectedGenres {
+ selected[genreID] = struct{}{}
+ }
+
+ out := make([]genreOption, 0, len(genres))
+ inactive := make([]genreOption, 0, len(genres))
+ for _, genre := range genres {
+ _, isSelected := selected[genre.MalID]
+ option := genreOption{Genre: genre, Selected: isSelected}
+ if isSelected {
+ out = append(out, option)
+ continue
+ }
+ inactive = append(inactive, option)
+ }
+
+ if len(out) > 0 && len(inactive) > 0 {
+ inactive[0].StartsInactive = true
+ }
+
+ return append(out, inactive...)
+}
+
func div(a, b float64) float64 {
if b == 0 {
return 0
diff --git a/templates/funcs_test.go b/templates/funcs_test.go
index 8639f97..8b1dfde 100644
--- a/templates/funcs_test.go
+++ b/templates/funcs_test.go
@@ -2,6 +2,7 @@ package templates
import (
"html/template"
+ "mal/internal/domain"
"testing"
)
@@ -445,6 +446,63 @@ func TestGenresParamsEmpty(t *testing.T) {
}
}
+func TestOrderedGenreOptionsSelectedFirst(t *testing.T) {
+ t.Parallel()
+
+ got := orderedGenreOptions(
+ []domain.Genre{
+ {MalID: 1, Name: "Action"},
+ {MalID: 2, Name: "Comedy"},
+ {MalID: 3, Name: "Shounen"},
+ {MalID: 4, Name: "Drama"},
+ },
+ []int{3, 1},
+ )
+
+ wantIDs := []int{1, 3, 2, 4}
+ if len(got) != len(wantIDs) {
+ t.Fatalf("expected %d genres, got %d", len(wantIDs), len(got))
+ }
+ for i, wantID := range wantIDs {
+ if got[i].Genre.MalID != wantID {
+ t.Fatalf("genre at index %d = %d, want %d", i, got[i].Genre.MalID, wantID)
+ }
+ }
+ if !got[0].Selected || !got[1].Selected {
+ t.Fatalf("expected first two genres to be selected, got %+v", got[:2])
+ }
+ if !got[2].StartsInactive {
+ t.Fatalf("expected first inactive genre to start divider, got %+v", got[2])
+ }
+ if got[3].StartsInactive {
+ t.Fatalf("expected divider only on first inactive genre, got %+v", got[3])
+ }
+}
+
+func TestOrderedGenreOptionsNoSelectedGenresPreservesOrder(t *testing.T) {
+ t.Parallel()
+
+ got := orderedGenreOptions(
+ []domain.Genre{
+ {MalID: 1, Name: "Action"},
+ {MalID: 2, Name: "Comedy"},
+ },
+ nil,
+ )
+
+ for i, option := range got {
+ if option.Selected {
+ t.Fatalf("expected genre at index %d to be inactive", i)
+ }
+ if option.StartsInactive {
+ t.Fatalf("expected no inactive divider without active genres")
+ }
+ if option.Genre.MalID != i+1 {
+ t.Fatalf("genre at index %d = %d, want %d", i, option.Genre.MalID, i+1)
+ }
+ }
+}
+
func TestPosterURLWebp(t *testing.T) {
t.Parallel()
diff --git a/templates/renderer.go b/templates/renderer.go
index 103fb2c..1d3ce4f 100644
--- a/templates/renderer.go
+++ b/templates/renderer.go
@@ -51,6 +51,7 @@ func rendererFuncs() template.FuncMap {
"browseURL": browseURL,
"genresParams": genresParams,
"hasGenre": hasGenre,
+ "orderedGenres": orderedGenreOptions,
"add": func(a, b int) int { return a + b },
"sub": func(a, b int) int { return a - b },
"mul": func(a, b float64) float64 { return a * b },