ui: remove schedule and split notifications
This commit is contained in:
@@ -296,28 +296,6 @@ func (h *Handler) HandleAPIDiscoverUpcoming(w http.ResponseWriter, r *http.Reque
|
||||
templates.DiscoverItems(res.Animes, "upcoming", page+1, res.HasNextPage).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleSchedule(w http.ResponseWriter, r *http.Request) {
|
||||
templates.Schedule().Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleAPISchedule(w http.ResponseWriter, r *http.Request) {
|
||||
day := r.URL.Query().Get("day")
|
||||
if day == "" {
|
||||
day = "monday"
|
||||
}
|
||||
|
||||
res, err := h.svc.GetSchedule(r.Context(), day)
|
||||
if err != nil {
|
||||
log.Printf("schedule error for %s: %v", day, err)
|
||||
http.Error(w, "Failed to fetch schedule", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
res.Animes = deduplicateAnimes(res.Animes)
|
||||
|
||||
templates.ScheduleDay(day, res.Animes).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleNotifications(w http.ResponseWriter, r *http.Request) {
|
||||
userID := userIDFromRequest(r)
|
||||
|
||||
@@ -326,14 +304,23 @@ func (h *Handler) HandleNotifications(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
watching, err := h.svc.GetWatchingAnime(r.Context(), userID)
|
||||
if err != nil {
|
||||
log.Printf("watching anime error: %v", err)
|
||||
http.Error(w, "Failed to fetch watching anime", http.StatusInternalServerError)
|
||||
return
|
||||
tab := r.URL.Query().Get("tab")
|
||||
if tab != "sequels" {
|
||||
tab = "tracking"
|
||||
}
|
||||
|
||||
templates.Notifications(watching).Render(r.Context(), w)
|
||||
var watching []templates.WatchingAnimeWithDetails
|
||||
if tab == "tracking" {
|
||||
var err error
|
||||
watching, err = h.svc.GetWatchingAnime(r.Context(), userID)
|
||||
if err != nil {
|
||||
log.Printf("watching anime error: %v", err)
|
||||
http.Error(w, "Failed to fetch watching anime", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
templates.Notifications(watching, tab).Render(r.Context(), w)
|
||||
}
|
||||
|
||||
func (h *Handler) HandleNotificationsUpcoming(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -61,10 +61,6 @@ func (s *Service) GetRelations(ctx context.Context, id int) ([]jikan.RelationEnt
|
||||
return s.jikanClient.GetFullRelations(ctx, id)
|
||||
}
|
||||
|
||||
func (s *Service) GetSchedule(ctx context.Context, day string) (jikan.ScheduleResult, error) {
|
||||
return s.jikanClient.GetSchedule(ctx, day)
|
||||
}
|
||||
|
||||
func (s *Service) GetRecommendations(ctx context.Context, animeID int, limit int) ([]jikan.Anime, error) {
|
||||
return s.jikanClient.GetRecommendations(ctx, animeID, limit)
|
||||
}
|
||||
|
||||
@@ -34,10 +34,8 @@ func NewRouter(cfg Config) http.Handler {
|
||||
|
||||
mux.HandleFunc("/", animeHandler.HandleCatalog)
|
||||
mux.HandleFunc("/discover", animeHandler.HandleDiscover)
|
||||
mux.HandleFunc("/schedule", animeHandler.HandleSchedule)
|
||||
mux.HandleFunc("/notifications", animeHandler.HandleNotifications)
|
||||
mux.HandleFunc("/notifications/upcoming", animeHandler.HandleNotificationsUpcoming)
|
||||
mux.HandleFunc("/api/schedule", animeHandler.HandleAPISchedule)
|
||||
mux.HandleFunc("/api/discover/airing", animeHandler.HandleAPIDiscoverAiring)
|
||||
mux.HandleFunc("/api/discover/upcoming", animeHandler.HandleAPIDiscoverUpcoming)
|
||||
mux.HandleFunc("/search", animeHandler.HandleSearch)
|
||||
|
||||
@@ -8,12 +8,11 @@ templ Layout(title string) {
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>mal</title>
|
||||
<title>{ title }</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/static/favicon.svg"/>
|
||||
<link rel="stylesheet" href="/static/css/style.css"/>
|
||||
<script src="https://unpkg.com/htmx.org@1.9.11"></script>
|
||||
<script src="/static/js/discover.js" defer></script>
|
||||
<script src="/static/js/schedule.js" defer></script>
|
||||
<script src="/static/js/anime.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -26,7 +25,6 @@ templ Layout(title string) {
|
||||
<div class="nav">
|
||||
<a href="/">Catalog</a>
|
||||
<a href="/discover">Discover</a>
|
||||
<a href="/schedule">Schedule</a>
|
||||
<a href="/notifications">Notifications</a>
|
||||
<a href="/watchlist">Watchlist</a>
|
||||
</div>
|
||||
|
||||
@@ -10,30 +10,33 @@ type WatchingAnimeWithDetails struct {
|
||||
Anime jikan.Anime
|
||||
}
|
||||
|
||||
templ Notifications(watching []WatchingAnimeWithDetails) {
|
||||
templ Notifications(watching []WatchingAnimeWithDetails, activeTab string) {
|
||||
@Layout("mal - notifications") {
|
||||
<div class="notifications-page">
|
||||
<h1>Airing shows (tracking)</h1>
|
||||
<p class="notifications-subtitle">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="empty-state-hint">Add currently airing shows to your watching list to see upcoming episodes here.</span>
|
||||
}
|
||||
} else {
|
||||
<div class="notifications-list">
|
||||
for _, item := range watching {
|
||||
@NotificationCard(item)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<h1 class="notifications-section-title">Discovered sequels</h1>
|
||||
<p class="notifications-subtitle">Because you've watched prequels.</p>
|
||||
|
||||
<div hx-get="/notifications/upcoming" hx-trigger="load, every 15s" hx-swap="innerHTML">
|
||||
@ui.LoadingIndicator("Syncing sequel graphs...")
|
||||
<h1>Notifications</h1>
|
||||
<div class="status-tabs">
|
||||
<a href="/notifications?tab=tracking" class={ tabClass(activeTab == "tracking") }>Tracking</a>
|
||||
<a href="/notifications?tab=sequels" class={ tabClass(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="notifications-subtitle">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="empty-state-hint">Add currently airing shows to your watching list to see upcoming episodes here.</span>
|
||||
}
|
||||
} else {
|
||||
<div class="notifications-list">
|
||||
for _, item := range watching {
|
||||
@NotificationCard(item)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
@@ -62,21 +65,27 @@ templ UpcomingSeasonsList(upcomingSeasons []database.GetUpcomingSeasonsRow) {
|
||||
templ renderSplitSeasons(upcomingSeasons []database.GetUpcomingSeasonsRow) {
|
||||
if airing, upcoming := splitUpcomingSeasons(upcomingSeasons); true {
|
||||
if len(airing) > 0 {
|
||||
<h2 class="notifications-group-title">Airing now (not tracked)</h2>
|
||||
<div class="notifications-list notifications-list-spaced">
|
||||
for _, item := range airing {
|
||||
@UpcomingSeasonCard(item)
|
||||
}
|
||||
</div>
|
||||
<section class="notifications-group notifications-list-spaced">
|
||||
<h2 class="notifications-group-title">Airing now</h2>
|
||||
<p class="notifications-group-note">These are the currently airing anime, but you're not tracking any of these.</p>
|
||||
<div class="notifications-list">
|
||||
for _, item := range airing {
|
||||
@UpcomingSeasonCard(item)
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
|
||||
if len(upcoming) > 0 {
|
||||
<h2 class="notifications-group-title">Announced & upcoming</h2>
|
||||
<div class="notifications-list">
|
||||
for _, item := range upcoming {
|
||||
@UpcomingSeasonCard(item)
|
||||
}
|
||||
</div>
|
||||
<section class="notifications-group">
|
||||
<h2 class="notifications-group-title">Announced & upcoming</h2>
|
||||
<p class="notifications-group-note">Newly announced or upcoming seasons related to anime you've watched.</p>
|
||||
<div class="notifications-list">
|
||||
for _, item := range upcoming {
|
||||
@UpcomingSeasonCard(item)
|
||||
}
|
||||
</div>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,9 +102,6 @@ templ UpcomingSeasonCard(item database.GetUpcomingSeasonsRow) {
|
||||
<div class="notification-title">
|
||||
{ displaySeasonTitle(item) }
|
||||
</div>
|
||||
<div class="notification-meta">
|
||||
<span class="notification-broadcast notification-muted">Because you watched { item.PrequelTitle }</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,92 +0,0 @@
|
||||
package templates
|
||||
|
||||
import "mal/internal/jikan"
|
||||
import "mal/internal/shared/ui"
|
||||
import "fmt"
|
||||
|
||||
templ Schedule() {
|
||||
@Layout("mal - schedule") {
|
||||
<div class="schedule-page">
|
||||
<h1>Weekly schedule</h1>
|
||||
<p class="schedule-subtitle">Airing times in JST</p>
|
||||
|
||||
<div class="schedule-tabs" data-tab-group="schedule">
|
||||
<button class="schedule-tab active" type="button" data-day="monday" data-schedule-tab>Mon</button>
|
||||
<button class="schedule-tab" type="button" data-day="tuesday" data-schedule-tab>Tue</button>
|
||||
<button class="schedule-tab" type="button" data-day="wednesday" data-schedule-tab>Wed</button>
|
||||
<button class="schedule-tab" type="button" data-day="thursday" data-schedule-tab>Thu</button>
|
||||
<button class="schedule-tab" type="button" data-day="friday" data-schedule-tab>Fri</button>
|
||||
<button class="schedule-tab" type="button" data-day="saturday" data-schedule-tab>Sat</button>
|
||||
<button class="schedule-tab" type="button" data-day="sunday" data-schedule-tab>Sun</button>
|
||||
</div>
|
||||
|
||||
<div id="schedule-content" hx-get="/api/schedule?day=monday" hx-trigger="load">
|
||||
@ui.LoadingIndicator("Loading schedule")
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
templ ScheduleDay(day string, animes []jikan.Anime) {
|
||||
<div class="schedule-day">
|
||||
<h2>{ dayTitle(day) }</h2>
|
||||
if len(animes) == 0 {
|
||||
<p class="no-anime">No anime scheduled.</p>
|
||||
} else {
|
||||
<div class="schedule-grid">
|
||||
for _, anime := range animes {
|
||||
@ScheduleAnimeCard(anime)
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
templ ScheduleAnimeCard(anime jikan.Anime) {
|
||||
@ui.AnimeCard(ui.AnimeCardProps{
|
||||
ID: anime.MalID,
|
||||
Title: anime.DisplayTitle(),
|
||||
ImageURL: anime.ImageURL(),
|
||||
Class: "schedule-card",
|
||||
ImgClass: "schedule-card-image",
|
||||
}) {
|
||||
<div class="schedule-card-info">
|
||||
<div class="schedule-card-title">{ anime.DisplayTitle() }</div>
|
||||
<div class="schedule-card-meta">
|
||||
if anime.Broadcast.Time != "" {
|
||||
<span class="schedule-time">{ anime.Broadcast.Time }</span>
|
||||
}
|
||||
if anime.Type != "" {
|
||||
<span class="schedule-type">{ anime.Type }</span>
|
||||
}
|
||||
if anime.Episodes > 0 {
|
||||
<span class="schedule-eps">{ fmt.Sprintf("%d ep", anime.Episodes) }</span>
|
||||
}
|
||||
</div>
|
||||
if anime.Score > 0 {
|
||||
<div class="schedule-card-score">★ { fmt.Sprintf("%.1f", anime.Score) }</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
func dayTitle(day string) string {
|
||||
switch day {
|
||||
case "monday":
|
||||
return "Monday"
|
||||
case "tuesday":
|
||||
return "Tuesday"
|
||||
case "wednesday":
|
||||
return "Wednesday"
|
||||
case "thursday":
|
||||
return "Thursday"
|
||||
case "friday":
|
||||
return "Friday"
|
||||
case "saturday":
|
||||
return "Saturday"
|
||||
case "sunday":
|
||||
return "Sunday"
|
||||
default:
|
||||
return day
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
;(function () {
|
||||
const contentSelector = '#schedule-content'
|
||||
|
||||
const loadDay = (tab) => {
|
||||
const day = tab.getAttribute('data-day')
|
||||
if (!day || typeof htmx === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
const tabs = document.querySelectorAll('[data-schedule-tab]')
|
||||
tabs.forEach((item) => item.classList.remove('active'))
|
||||
tab.classList.add('active')
|
||||
htmx.ajax('GET', `/api/schedule?day=${day}`, contentSelector)
|
||||
}
|
||||
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target
|
||||
if (!(target instanceof Element)) {
|
||||
return
|
||||
}
|
||||
|
||||
const tab = target.closest('[data-schedule-tab]')
|
||||
if (!tab) {
|
||||
return
|
||||
}
|
||||
|
||||
loadDay(tab)
|
||||
})
|
||||
})()
|
||||
Reference in New Issue
Block a user