185 lines
6.4 KiB
Plaintext
185 lines
6.4 KiB
Plaintext
package templates
|
|
|
|
import "mal/internal/jikan"
|
|
import "mal/internal/database"
|
|
import "mal/internal/shared/ui"
|
|
import "fmt"
|
|
import "strings"
|
|
|
|
type WatchingAnimeWithDetails struct {
|
|
Entry database.GetWatchingAnimeRow
|
|
Anime jikan.Anime
|
|
}
|
|
|
|
templ Notifications(watching []WatchingAnimeWithDetails, activeTab string) {
|
|
@Layout("mal - notifications", true) {
|
|
<div class="grid gap-4">
|
|
<h1>Notifications</h1>
|
|
<div class="mb-3 flex flex-wrap gap-2 max-md:flex-nowrap max-md:overflow-x-auto max-md:pb-1">
|
|
<a href="/notifications?tab=tracking" class={ statusTabClass(activeTab == "tracking") }>Tracking</a>
|
|
<a href="/notifications?tab=sequels" class={ statusTabClass(activeTab == "sequels") }>Sequels</a>
|
|
</div>
|
|
|
|
if activeTab == "sequels" {
|
|
<div hx-get="/notifications/upcoming" hx-trigger="load, every 15s" hx-swap="innerHTML">
|
|
@ui.LoadingIndicator("Syncing sequel graphs...")
|
|
</div>
|
|
} else {
|
|
<p class="m-0 text-sm text-(--text-muted)">Shows you're currently watching or planning to watch.</p>
|
|
if len(watching) == 0 {
|
|
@ui.EmptyState("No airing anime in your watching list.") {
|
|
<span class="text-sm text-(--text-muted)">Add currently airing shows to your watching list to see upcoming episodes here.</span>
|
|
}
|
|
} else {
|
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:gap-4 lg:grid-cols-4 xl:grid-cols-5">
|
|
for _, item := range watching {
|
|
@NotificationCard(item)
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
</div>
|
|
}
|
|
}
|
|
|
|
func splitUpcomingSeasons(items []database.GetUpcomingSeasonsRow) (airing []database.GetUpcomingSeasonsRow, upcoming []database.GetUpcomingSeasonsRow) {
|
|
for _, item := range items {
|
|
if item.Status.Valid && item.Status.String == "Currently Airing" {
|
|
airing = append(airing, item)
|
|
} else {
|
|
upcoming = append(upcoming, item)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
templ UpcomingSeasonsList(upcomingSeasons []database.GetUpcomingSeasonsRow) {
|
|
if len(upcomingSeasons) == 0 {
|
|
@ui.EmptyState("No upcoming seasons for anime you've watched.") {
|
|
<span class="text-sm text-(--text-muted)">As you watch more shows, new seasons will appear here.</span>
|
|
}
|
|
} else {
|
|
@renderSplitSeasons(upcomingSeasons)
|
|
}
|
|
}
|
|
|
|
templ renderSplitSeasons(upcomingSeasons []database.GetUpcomingSeasonsRow) {
|
|
if airing, upcoming := splitUpcomingSeasons(upcomingSeasons); true {
|
|
if len(airing) > 0 {
|
|
<section class="mb-4 grid gap-3">
|
|
<h2 class="m-0 text-xl font-semibold leading-tight">Airing now</h2>
|
|
<p class="m-0 text-sm text-(--text-muted)">These are the currently airing anime, but you're not tracking any of these.</p>
|
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:gap-4 lg:grid-cols-4 xl:grid-cols-5">
|
|
for _, item := range airing {
|
|
@UpcomingSeasonCard(item)
|
|
}
|
|
</div>
|
|
</section>
|
|
}
|
|
|
|
if len(upcoming) > 0 {
|
|
<section class="grid gap-3">
|
|
<h2 class="m-0 text-xl font-semibold leading-tight">Announced & upcoming</h2>
|
|
<p class="m-0 text-sm text-(--text-muted)">Newly announced or upcoming seasons related to anime you've watched.</p>
|
|
<div class="grid grid-cols-2 gap-3 sm:grid-cols-3 md:gap-4 lg:grid-cols-4 xl:grid-cols-5">
|
|
for _, item := range upcoming {
|
|
@UpcomingSeasonCard(item)
|
|
}
|
|
</div>
|
|
</section>
|
|
}
|
|
}
|
|
}
|
|
|
|
templ UpcomingSeasonCard(item database.GetUpcomingSeasonsRow) {
|
|
@ui.AnimeCard(ui.AnimeCardProps{
|
|
ID: int(item.ID),
|
|
Title: displaySeasonTitle(item),
|
|
ImageURL: item.ImageUrl,
|
|
Class: "notification-card min-w-0 flex flex-col bg-transparent text-inherit no-underline",
|
|
HideTitle: true,
|
|
}) {
|
|
<div class="mt-2 grid gap-1 p-0" data-notification-content>
|
|
<div class="line-clamp-2 text-sm leading-snug text-(--text)">
|
|
{ displaySeasonTitle(item) }
|
|
</div>
|
|
<div class="flex flex-wrap gap-2">
|
|
if item.Status.Valid {
|
|
<span class="text-xs text-(--text-faint)">{ seasonStatusLabel(item.Status.String) }</span>
|
|
}
|
|
if strings.TrimSpace(item.PrequelTitle) != "" {
|
|
<span class="text-xs text-(--text-faint)">{ fmt.Sprintf("Sequel to %s", item.PrequelTitle) }</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
func displaySeasonTitle(entry database.GetUpcomingSeasonsRow) string {
|
|
return database.DisplayTitle(entry.TitleEnglish, entry.TitleJapanese, entry.TitleOriginal)
|
|
}
|
|
|
|
templ NotificationCard(item WatchingAnimeWithDetails) {
|
|
@ui.AnimeCard(ui.AnimeCardProps{
|
|
ID: int(item.Entry.AnimeID),
|
|
Title: displayTitle(item.Entry),
|
|
ImageURL: item.Entry.ImageUrl,
|
|
Class: "notification-card min-w-0 flex flex-col bg-transparent text-inherit no-underline",
|
|
HideTitle: true,
|
|
}) {
|
|
<div class="mt-2 grid gap-1 p-0" data-notification-content>
|
|
<div class="line-clamp-2 text-sm leading-snug text-(--text)">
|
|
{ displayTitle(item.Entry) }
|
|
</div>
|
|
<div class="flex flex-wrap gap-2">
|
|
if item.Anime.Broadcast.String != "" {
|
|
<span class="text-xs text-(--text-faint)" data-jst-text={ item.Anime.Broadcast.String } data-broadcast-day={ item.Anime.Broadcast.Day } data-broadcast-time={ item.Anime.Broadcast.Time } data-broadcast-timezone={ item.Anime.Broadcast.Timezone }>{ item.Anime.Broadcast.String }</span>
|
|
<span class="text-xs text-(--text-faint)" data-next-airing="pending">Calculating next episode time...</span>
|
|
}
|
|
if item.Anime.Episodes > 0 {
|
|
<span class="text-xs text-(--text-faint)">
|
|
if item.Entry.CurrentEpisode.Valid {
|
|
{ fmt.Sprintf("%d / %d eps", item.Entry.CurrentEpisode.Int64, item.Anime.Episodes) }
|
|
} else {
|
|
{ fmt.Sprintf("0 / %d eps", item.Anime.Episodes) }
|
|
}
|
|
</span>
|
|
} else if item.Entry.CurrentEpisode.Valid && item.Entry.CurrentEpisode.Int64 > 0 {
|
|
<span class="text-xs text-(--text-faint)">
|
|
{ fmt.Sprintf("%d eps watched", item.Entry.CurrentEpisode.Int64) }
|
|
</span>
|
|
}
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
|
|
func displayTitle(entry database.GetWatchingAnimeRow) string {
|
|
return database.DisplayTitle(entry.TitleEnglish, entry.TitleJapanese, entry.TitleOriginal)
|
|
}
|
|
|
|
func seasonStatusLabel(status string) string {
|
|
statusText := strings.TrimSpace(status)
|
|
if statusText == "" {
|
|
return ""
|
|
}
|
|
|
|
if statusText == "Currently Airing" {
|
|
return "Airing now"
|
|
}
|
|
|
|
if statusText == "Not yet aired" {
|
|
return "Upcoming"
|
|
}
|
|
|
|
return statusText
|
|
}
|
|
|
|
func statusTabClass(active bool) string {
|
|
base := "shrink-0 whitespace-nowrap bg-(--panel-soft) px-2 py-1 text-xs text-(--text-muted) no-underline hover:bg-(--surface-tab-hover) hover:text-(--text) hover:no-underline"
|
|
if active {
|
|
return "shrink-0 whitespace-nowrap bg-(--surface-tab-active) px-2 py-1 text-xs text-(--accent) no-underline hover:no-underline"
|
|
}
|
|
return base
|
|
}
|