package templates import ( "encoding/json" "fmt" "mal/internal/jikan" "mal/internal/shared/ui" "net/url" "strconv" ) // WatchPageData holds the data needed for the watch page type WatchPageData struct { MalID int Title string TitleEnglish string TitleJapanese string ImageURL string Airing bool CurrentEpisode string TotalEpisodes int StartTimeSeconds float64 CurrentStatus string InitialMode string AvailableModes []string ModeSources map[string]ModeSource Segments []SkipSegment } // ModeSource represents a stream source for a specific mode (dub/sub) type ModeSource struct { URL string `json:"url"` Referer string `json:"referer"` Subtitles []SubtitleItem `json:"subtitles"` } // SubtitleItem represents a subtitle track type SubtitleItem struct { Lang string `json:"lang"` URL string `json:"url"` Referer string `json:"referer"` } // SkipSegment represents a skippable segment (intro/outro) type SkipSegment struct { Type string `json:"type"` Start float64 `json:"start"` End float64 `json:"end"` } templ WatchPage(anime jikan.Anime, data WatchPageData) { @Layout(fmt.Sprintf("%s - episode %s", anime.DisplayTitle(), data.CurrentEpisode), true) {
@VideoPlayer(data)
if canGoPrevEpisode(data.CurrentEpisode) { ◀ Prev } else { ◀ Prev } if canGoNextEpisode(data.CurrentEpisode, anime.Episodes) { Next ▶ } else { Next ▶ } @WatchlistDropdown(anime.MalID, anime.Title, anime.TitleEnglish, anime.TitleJapanese, anime.ImageURL(), data.CurrentStatus, anime.Airing)

Watch more seasons of this anime

@ui.LoadingIndicator("Loading relations")
} } templ LoadingIndicatorSmall() {
} templ EpisodeList(episodes []jikan.Episode, currentEpisode string, animeID int) { if len(episodes) == 0 {

No episodes available

} else {
for _, ep := range episodes { @EpisodeItem(ep, currentEpisode, animeID) }
} } templ EpisodeItem(episode jikan.Episode, currentEpisode string, animeID int) { {{ isCurrent := fmt.Sprintf("%d", episode.MalID) == currentEpisode }} { fmt.Sprintf("%d", episode.MalID) } if episode.Title != "" { { episode.Title } } else { Episode { fmt.Sprintf("%d", episode.MalID) } }
if episode.Filler { Filler } if episode.Recap { Recap } if isCurrent { }
} templ VideoPlayer(data WatchPageData) { {{ streamURL := buildStreamURL(data.InitialMode, data.ModeSources) }} {{ hasDub := modeAvailable(data.AvailableModes, "dub") }} {{ hasSub := modeAvailable(data.AvailableModes, "sub") }}
00:00 / 00:00
} func buildStreamURL(mode string, modeSources map[string]ModeSource) string { stateJSON, _ := json.Marshal(modeSources) return fmt.Sprintf("/watch/proxy/stream?mode=%s&state=%s", url.QueryEscape(mode), url.QueryEscape(string(stateJSON))) } func toJSON(v interface{}) string { b, _ := json.Marshal(v) return string(b) } func episodeWithOffsetURL(animeID int, currentEpisode string, offset int) string { episodeID, err := strconv.Atoi(currentEpisode) if err != nil { episodeID = 1 } nextEpisode := episodeID + offset if nextEpisode < 1 { nextEpisode = 1 } return fmt.Sprintf("/watch/%d/%d", animeID, nextEpisode) } func canGoPrevEpisode(currentEpisode string) bool { episodeID, err := strconv.Atoi(currentEpisode) if err != nil { return false } return episodeID > 1 } func canGoNextEpisode(currentEpisode string, totalEpisodes int) bool { if totalEpisodes <= 0 { return true } episodeID, err := strconv.Atoi(currentEpisode) if err != nil { return false } return episodeID < totalEpisodes } func modeAvailable(modes []string, mode string) bool { for _, value := range modes { if value == mode { return true } } return false } func modeButtonTitle(label string, enabled bool) string { if enabled { return label } return label + " unavailable for this episode" }