ui: update AnimeCard to support children and integrate across anime, notifications, and schedule templates
This commit is contained in:
@@ -6,17 +6,57 @@ type AnimeCardProps struct {
|
|||||||
ID int
|
ID int
|
||||||
Title string
|
Title string
|
||||||
ImageURL string
|
ImageURL string
|
||||||
|
// Options to customize the card behavior
|
||||||
|
Class string // override default wrapper class
|
||||||
|
ImgClass string // override default image class
|
||||||
|
TitleClass string // override default title class
|
||||||
|
CurrentNode bool // if true, renders a div instead of an anchor tag (useful for graph nodes)
|
||||||
}
|
}
|
||||||
|
|
||||||
templ AnimeCard(props AnimeCardProps) {
|
templ AnimeCard(props AnimeCardProps) {
|
||||||
<a href={ templ.URL(fmt.Sprintf("/anime/%d", props.ID)) }>
|
if props.CurrentNode {
|
||||||
if props.ImageURL != "" {
|
<div class={ defaultString(props.Class, "catalog-item") }>
|
||||||
<img src={ props.ImageURL } alt={ props.Title } class="catalog-thumb" loading="lazy"/>
|
if props.ImageURL != "" {
|
||||||
} else {
|
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "catalog-thumb") } loading="lazy"/>
|
||||||
<div class="no-image">No image</div>
|
} else {
|
||||||
}
|
<div class="no-image">No image</div>
|
||||||
</a>
|
}
|
||||||
<div class="catalog-title">
|
<div class={ defaultString(props.TitleClass, "catalog-title") }>
|
||||||
{ props.Title }
|
{ props.Title }
|
||||||
</div>
|
</div>
|
||||||
|
{ children... }
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
<a href={ templ.URL(fmt.Sprintf("/anime/%d", props.ID)) } class={ props.Class }>
|
||||||
|
if props.Class == "notification-card" || props.Class == "schedule-card" {
|
||||||
|
<div class={ defaultString(props.ImgClass, "schedule-card-image") }>
|
||||||
|
if props.ImageURL != "" {
|
||||||
|
<img src={ props.ImageURL } alt={ props.Title } loading="lazy"/>
|
||||||
|
} else {
|
||||||
|
<div class="no-image">No image</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
} else {
|
||||||
|
if props.ImageURL != "" {
|
||||||
|
<img src={ props.ImageURL } alt={ props.Title } class={ defaultString(props.ImgClass, "catalog-thumb") } loading="lazy"/>
|
||||||
|
} else {
|
||||||
|
<div class="no-image">No image</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if props.Class != "notification-card" && props.Class != "schedule-card" {
|
||||||
|
<div class={ defaultString(props.TitleClass, "catalog-title") }>
|
||||||
|
{ props.Title }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
{ children... }
|
||||||
|
</a>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultString(val, fallback string) string {
|
||||||
|
if val == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return val
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,25 +282,15 @@ templ AnimeRelationsList(relations []jikan.RelationEntry) {
|
|||||||
if len(relations) > 1 {
|
if len(relations) > 1 {
|
||||||
<div class="relations-grid">
|
<div class="relations-grid">
|
||||||
for _, rel := range relations {
|
for _, rel := range relations {
|
||||||
if rel.IsCurrent {
|
@ui.AnimeCard(ui.AnimeCardProps{
|
||||||
<div class="relation-card current">
|
ID: rel.Anime.MalID,
|
||||||
if rel.Anime.ImageURL() != "" {
|
Title: rel.Anime.DisplayTitle(),
|
||||||
<img src={ rel.Anime.ImageURL() } alt={ rel.Anime.DisplayTitle() } class="relation-thumb"/>
|
ImageURL: rel.Anime.ImageURL(),
|
||||||
} else {
|
Class: "relation-card" + func() string { if rel.IsCurrent { return " current" }; return "" }(),
|
||||||
<div class="no-image">No image</div>
|
ImgClass: "relation-thumb",
|
||||||
}
|
TitleClass: "relation-title",
|
||||||
<div class="relation-title">{ rel.Anime.DisplayTitle() }</div>
|
CurrentNode: rel.IsCurrent,
|
||||||
</div>
|
})
|
||||||
} else {
|
|
||||||
<a href={ templ.URL(fmt.Sprintf("/anime/%d", rel.Anime.MalID)) } class="relation-card">
|
|
||||||
if rel.Anime.ImageURL() != "" {
|
|
||||||
<img src={ rel.Anime.ImageURL() } alt={ rel.Anime.DisplayTitle() } class="relation-thumb"/>
|
|
||||||
} else {
|
|
||||||
<div class="no-image">No image</div>
|
|
||||||
}
|
|
||||||
<div class="relation-title">{ rel.Anime.DisplayTitle() }</div>
|
|
||||||
</a>
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
@@ -312,14 +302,14 @@ templ AnimeRecommendations(recs []jikan.Anime) {
|
|||||||
if len(recs) > 0 {
|
if len(recs) > 0 {
|
||||||
<div class="relations-grid">
|
<div class="relations-grid">
|
||||||
for _, anime := range recs {
|
for _, anime := range recs {
|
||||||
<a href={ templ.URL(fmt.Sprintf("/anime/%d", anime.MalID)) } class="relation-card">
|
@ui.AnimeCard(ui.AnimeCardProps{
|
||||||
if anime.ImageURL() != "" {
|
ID: anime.MalID,
|
||||||
<img src={ anime.ImageURL() } alt={ anime.DisplayTitle() } class="relation-thumb"/>
|
Title: anime.DisplayTitle(),
|
||||||
} else {
|
ImageURL: anime.ImageURL(),
|
||||||
<div class="no-image">No image</div>
|
Class: "relation-card",
|
||||||
}
|
ImgClass: "relation-thumb",
|
||||||
<div class="relation-title">{ anime.DisplayTitle() }</div>
|
TitleClass: "relation-title",
|
||||||
</a>
|
})
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package templates
|
|||||||
|
|
||||||
import "mal/internal/jikan"
|
import "mal/internal/jikan"
|
||||||
import "mal/internal/database"
|
import "mal/internal/database"
|
||||||
|
import "mal/internal/shared/ui"
|
||||||
import "fmt"
|
import "fmt"
|
||||||
|
|
||||||
type WatchingAnimeWithDetails struct {
|
type WatchingAnimeWithDetails struct {
|
||||||
@@ -32,10 +33,7 @@ templ Notifications(watching []WatchingAnimeWithDetails) {
|
|||||||
<p class="notifications-subtitle">Because you've watched prequels.</p>
|
<p class="notifications-subtitle">Because you've watched prequels.</p>
|
||||||
|
|
||||||
<div hx-get="/notifications/upcoming" hx-trigger="load, every 15s" hx-swap="innerHTML">
|
<div hx-get="/notifications/upcoming" hx-trigger="load, every 15s" hx-swap="innerHTML">
|
||||||
<div class="loading-indicator">
|
@ui.LoadingIndicator("Syncing sequel graphs...")
|
||||||
<div class="loading-dot"></div><div class="loading-dot"></div><div class="loading-dot"></div>
|
|
||||||
<span>Syncing sequel graphs...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -86,14 +84,13 @@ templ renderSplitSeasons(upcomingSeasons []database.GetUpcomingSeasonsRow) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
templ UpcomingSeasonCard(item database.GetUpcomingSeasonsRow) {
|
templ UpcomingSeasonCard(item database.GetUpcomingSeasonsRow) {
|
||||||
<a href={ templ.URL(fmt.Sprintf("/anime/%d", item.ID)) } class="notification-card">
|
@ui.AnimeCard(ui.AnimeCardProps{
|
||||||
<div class="notification-image">
|
ID: int(item.ID),
|
||||||
if item.ImageUrl != "" {
|
Title: displaySeasonTitle(item),
|
||||||
<img src={ item.ImageUrl } alt={ displaySeasonTitle(item) } loading="lazy"/>
|
ImageURL: item.ImageUrl,
|
||||||
} else {
|
Class: "notification-card",
|
||||||
<div class="no-image">No image</div>
|
ImgClass: "notification-image",
|
||||||
}
|
}) {
|
||||||
</div>
|
|
||||||
<div class="notification-content">
|
<div class="notification-content">
|
||||||
<div class="notification-title">
|
<div class="notification-title">
|
||||||
{ displaySeasonTitle(item) }
|
{ displaySeasonTitle(item) }
|
||||||
@@ -102,7 +99,7 @@ templ UpcomingSeasonCard(item database.GetUpcomingSeasonsRow) {
|
|||||||
<span class="notification-broadcast" style="color: var(--text-muted) !important;">Because you watched { item.PrequelTitle }</span>
|
<span class="notification-broadcast" style="color: var(--text-muted) !important;">Because you watched { item.PrequelTitle }</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func displaySeasonTitle(entry database.GetUpcomingSeasonsRow) string {
|
func displaySeasonTitle(entry database.GetUpcomingSeasonsRow) string {
|
||||||
@@ -110,14 +107,13 @@ func displaySeasonTitle(entry database.GetUpcomingSeasonsRow) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
templ NotificationCard(item WatchingAnimeWithDetails) {
|
templ NotificationCard(item WatchingAnimeWithDetails) {
|
||||||
<a href={ templ.URL(fmt.Sprintf("/anime/%d", item.Entry.AnimeID)) } class="notification-card">
|
@ui.AnimeCard(ui.AnimeCardProps{
|
||||||
<div class="notification-image">
|
ID: int(item.Entry.AnimeID),
|
||||||
if item.Entry.ImageUrl != "" {
|
Title: displayTitle(item.Entry),
|
||||||
<img src={ item.Entry.ImageUrl } alt={ displayTitle(item.Entry) } loading="lazy"/>
|
ImageURL: item.Entry.ImageUrl,
|
||||||
} else {
|
Class: "notification-card",
|
||||||
<div class="no-image">No image</div>
|
ImgClass: "notification-image",
|
||||||
}
|
}) {
|
||||||
</div>
|
|
||||||
<div class="notification-content">
|
<div class="notification-content">
|
||||||
<div class="notification-title">
|
<div class="notification-title">
|
||||||
{ displayTitle(item.Entry) }
|
{ displayTitle(item.Entry) }
|
||||||
@@ -141,7 +137,7 @@ templ NotificationCard(item WatchingAnimeWithDetails) {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func displayTitle(entry database.GetWatchingAnimeRow) string {
|
func displayTitle(entry database.GetWatchingAnimeRow) string {
|
||||||
|
|||||||
@@ -52,14 +52,13 @@ templ ScheduleDay(day string, animes []jikan.Anime) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
templ ScheduleAnimeCard(anime jikan.Anime) {
|
templ ScheduleAnimeCard(anime jikan.Anime) {
|
||||||
<a href={ templ.URL(fmt.Sprintf("/anime/%d", anime.MalID)) } class="schedule-card">
|
@ui.AnimeCard(ui.AnimeCardProps{
|
||||||
<div class="schedule-card-image">
|
ID: anime.MalID,
|
||||||
if anime.ImageURL() != "" {
|
Title: anime.DisplayTitle(),
|
||||||
<img src={ anime.ImageURL() } alt={ anime.DisplayTitle() } loading="lazy"/>
|
ImageURL: anime.ImageURL(),
|
||||||
} else {
|
Class: "schedule-card",
|
||||||
<div class="no-image">No image</div>
|
ImgClass: "schedule-card-image",
|
||||||
}
|
}) {
|
||||||
</div>
|
|
||||||
<div class="schedule-card-info">
|
<div class="schedule-card-info">
|
||||||
<div class="schedule-card-title">{ anime.DisplayTitle() }</div>
|
<div class="schedule-card-title">{ anime.DisplayTitle() }</div>
|
||||||
<div class="schedule-card-meta">
|
<div class="schedule-card-meta">
|
||||||
@@ -77,5 +76,5 @@ templ ScheduleAnimeCard(anime jikan.Anime) {
|
|||||||
<div class="schedule-card-score">★ { fmt.Sprintf("%.1f", anime.Score) }</div>
|
<div class="schedule-card-score">★ { fmt.Sprintf("%.1f", anime.Score) }</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user